STM32F1SPI标准接口程序以及模拟SPI标准接口程序讲解

一、SPI标准接口初始化:

1、SPI 内部结构简明图

2、上一篇文章提到,spi标准协议包含4条信号线、时钟相位、时钟极性、数据单字节从高位到低位传输、片选信号可以由软件控制,也可以由硬件控制,依据以上内容初始化SPI标准接口程序如下:

void SPI2_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//PORTB 时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE );//①SPI2 时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //①初始化 GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15 上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置 SPI 全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置 SPI 工作模式:设置为主 SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8 位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//选择了串行时钟的稳态:时钟悬空高
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 信号由硬件管理SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //预分频 256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //②根据指定的参数初始化外设 SPIx 寄存器
SPI_Cmd(SPI2, ENABLE); //③使能 SPI 外设
SPI2_ReadWriteByte(0xff); //④启动传输
}

(1)IO口初始化:
要用 SPI2,第一步就要使能 SPI2 的时钟。其次要设置 SPI2 的相关引脚为复用输出,这样才会连接到 SPI2 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里使用的是 PB13、14、15 这 3 个(SCK.、MISO、MOSI,CS 使用软件管理方式),所以设置这三个为复用 IO。
(2)初始化SPI标准接口(结构体):
第一个参数 SPI_Direction 是用来设置 SPI 的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式,这里选择全双工模式 SPI_Direction_2Lines_FullDuplex。
第二个参数 SPI_Mode 用来设置 SPI 的主从模式,这里设置为主机模式 SPI_Mode_Master,当然也可以选择为从机模式 SPI_Mode_Slave。
第三个参数 SPI_DataSiz 为 8 位还是 16 位帧格式选择项,这里是 8 位传输,选择SPI_DataSize_8b。
第四个参数 SPI_CPOL 用来设置时钟极性,设置串行同步时钟的空闲状态为高电平所以选择 SPI_CPOL_High。
第五个参数 SPI_CPHA 用来设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升或下降)数据被采样,可以为第一个或者第二个条边沿采集,这里选择第二个跳变沿,所以选择 SPI_CPHA_2Edge
第六个参数 SPI_NSS 设置 NSS 信号由硬件(NSS 管脚)还是软件控制,这里通过软件控制 NSS 关键,而不是硬件自动控制,所以选择 SPI_NSS_Soft。
第七个参数 SPI_BaudRatePrescaler 很关键,就是设置 SPI 波特率预分频值也就是决定 SPI 的时钟的参数,从不分频道 256 分频 8 个可选值,初始化的时候选择 256 分频值SPI_BaudRatePrescaler_256, 传输速度为 36M/256=140.625KHz。
第八个参数 SPI_FirstBit 设置数据传输顺序是 MSB 位在前还是 LSB 位在前,这里选择SPI_FirstBit_MSB 高位在前。
第九个参数 SPI_CRCPolynomial 是用来设置 CRC 校验多项式,提高通信可靠性,大于 1 即可。
(3)使能 SPI2:
初始化完成之后接下来是要使能 SPI2 通信了,在使能 SPI2 之后,就可以开始 SPI 通讯了。使能 SPI2 的方法是:
SPI_Cmd(SPI2, ENABLE); //使能 SPI 外设
(4)启动数据传输:
外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。
3、标准SPI接口初始化程序写完后,开始写数据传输程序,程序如下:

u8 SPI2_ReadWriteByte(u8 TxData)
{u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //等待发送区空
{retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通过外设 SPIx 发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //等待接收
//完一个 byte
{ retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通过 SPIx 最近接收的数据
}

(1)SPI 传输数据:
通信接口需要有发送数据和接受数据的函数,固件库提供的发送数据函数原型为:
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
往 SPIx 数据寄存器写入数据 Data,从而实现发送。
固件库提供的接受数据函数原型为:
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
从 SPIx 数据寄存器读出接受到的数据。
(2)获取SPI 传输状态:
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE);
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);
***(3)改变SPI 波特率预分频值从而改变数据传输速度的程序如下:***

//SPI 速度设置函数
//SpeedSet://SPI_BaudRatePrescaler_256 256 分频 (SPI 281.25K@sys 72M)
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
SPI2->CR1&=0XFFC7;
SPI2->CR1|=SPI_BaudRatePrescaler; //设置 SPI2 速度
SPI_Cmd(SPI2,ENABLE);
}

至此SPI标准接口初始化程序就已完成了

二、STM32模拟SPI标准接口设计思路以及部分程序讲解

1、既然SPI标准协议是用4条信号线,那么首先需要初始化4个引脚,其中MISO引脚要初始化为输入引脚,其他三条信号线SCL,CS,MOSI引脚都初始化为输出引脚。
2、片选信号与时钟极性在初始化时将对应的引脚设置为高电平或低电平就对应不同的模式(所谓的时钟极性也就是在空闲状态下时钟信号的高低电平状态)
3、初始化设置好后就该写读写数据函数了,那么读写数据函数分为两种形式:
(1)一种是分开读写,也就是读是一个函数,写是一个函数,分时传输数据,程序如下:

/*
* 函数名:void SPI_WriteByte(uint8_t data)
* 输入参数:data -> 要写的数据
* 输出参数:无
* 返回值:无
* 函数作用:模拟 SPI 写一个字节
*/
void SPI_WriteByte(uint8_t data)
{uint8_t i = 0;
uint8_t temp = 0;
for(i=0; i<8; i++) {temp = ((data&0x80)==0x80)? 1:0;
data = data<<1;
SPI_CLK(0); //CPOL=0
SPI_MOSI(temp);
SPI_Delay();
SPI_CLK(1); //CPHA=0
SPI_Delay(); }
SPI_CLK(0); }
/*
* 函数名:uint8_t SPI_ReadByte(void)
* 输入参数:
* 输出参数:无
* 返回值:读到的数据
* 函数作用:模拟 SPI 读一个字节
*/
uint8_t SPI_ReadByte(void) {uint8_t i = 0;
uint8_t read_data = 0xFF;
for(i=0; i<8; i++) {read_data = read_data << 1;
SPI_CLK(0);
SPI_Delay();
SPI_CLK(1);
SPI_Delay();
if(SPI_MISO()==1) {read_data = read_data + 1; } }
SPI_CLK(0);
return read_data;
}

(2)另一种是读写数据是一个函数,同步传输数据,比如在时钟的上升沿是写数据,那么在时钟的下降沿就是读数据,程序如下:

/*
* 函数名:uint8_t SPI_WriteReadByte(uint8_t data)
* 输入参数:data -> 要写的一个字节数据
* 输出参数:无
* 返回值:读到的数据
* 函数作用:模拟 SPI 读写一个字节
*/
uint8_t SPI_WriteReadByte(uint8_t data)
{uint8_t i = 0;
uint8_t temp = 0;
uint8_t read_data = 0xFF;
for(i=0;i<8;i++) {temp = ((data&0x80)==0x80)? 1:0;
data = data<<1;
read_data = read_data<<1;
SPI_CLK(0);
SPI_MOSI(temp);
SPI_Delay();
SPI_CLK(1);
SPI_Delay();
if(SPI_MISO()==1) {read_data = read_data + 1; } }
SPI_CLK(0);
return read_data;
}

至此SPI标准接口模拟程序就已完成了

三、FLASH 介绍

1、Flash物理特性:
只能写0,不能写1,也就是说,当bit=1时,此bit可以写数据改变这个bit值,当bit=0时,说明此bit有数值了,不能再写了,只能擦除后再写数据。
2、结构组成:
W25Q128 将 16M 的容量分为 256 个块(Block),每个块大小为 64K 字节,每个块又分为16 个扇区(Sector),每个扇区 4K 个字节。W25Q128 的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。这样我们需要给 W25Q128 开辟一个至少 4K 的缓存区,这样对 SRAM 要求比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。
3、W25Q128 相关指令与函数
(1)初始化片选信号线

void W25QXX_CS(void)
{   GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;  // PB12 推挽 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);
}

#define W25QXX_CS PBout(12) //W25QXX的片选信号
(2)读写状态寄存器(0x05,0x01):

u8 W25QXX_ReadSR(void)
{  u8 byte=0;   W25QXX_CS=0;                            //使能器件   SPI2_ReadWriteByte(0x05);               //发送读取状态寄存器命令    byte=SPI2_ReadWriteByte(0Xff);          //读取一个字节  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(0x01);//发送写取状态寄存器命令    SPI2_ReadWriteByte(sr);                 //写入一个字节  W25QXX_CS=1;                            //取消片选
}

(3)等待空闲,进入掉电模式(0xB9),唤醒flash(0xAB)函数

void W25QXX_Wait_Busy(void)
{   while((W25QXX_ReadSR()&0x01)==0x01);          // 等待BUSY位清空
}
void W25QXX_PowerDown(void)
{ W25QXX_CS=0;                                 //使能器件   SPI2_ReadWriteByte(W25X_PowerDown);        //发送掉电命令  W25QXX_CS=1;                             //取消片选            delay_us(3);                               //等待TPD
}
//唤醒
void W25QXX_WAKEUP(void)
{  W25QXX_CS=0;                                //使能器件   SPI2_ReadWriteByte(W25X_ReleasePowerDown); //  send W25X_PowerDown command 0xAB    W25QXX_CS=1;                               //取消片选            delay_us(3);                              //等待TRES1
}   

(4)写使能与写禁止(0x06,0x04):

//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{W25QXX_CS=0;                              //使能器件   SPI2_ReadWriteByte(0x06);            //发送写使能  W25QXX_CS=1;                            //取消片选
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{  W25QXX_CS=0;                            //使能器件   SPI2_ReadWriteByte(0x04);              //发送写禁止指令    W25QXX_CS=1;                            //取消片选
}

(5)读制造商和芯片ID(90h)
先发送命令,再发送24位空字节,再接收16位地址,程序如下:

u16 W25QXX_ReadID(void)
{u16 Temp = 0;   W25QXX_CS=0;                 SPI2_ReadWriteByte(0x90);//发送读取ID命令     SPI2_ReadWriteByte(0x00);       SPI2_ReadWriteByte(0x00);       SPI2_ReadWriteByte(0x00);                  Temp|=SPI2_ReadWriteByte(0xFF)<<8;  Temp|=SPI2_ReadWriteByte(0xFF);   W25QXX_CS=1;                  return Temp;
}

(6)读数据(0x03):
先发送读命令,再发送24位地址,再读取指定大小的数据,程序如下:

void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{ u16 i;                                            W25QXX_CS=0;                               //使能器件   SPI2_ReadWriteByte(W25X_ReadData);             //发送读取命令   SPI2_ReadWriteByte((u8)((ReadAddr)>>16));      //发送24bit地址    SPI2_ReadWriteByte((u8)((ReadAddr)>>8));   SPI2_ReadWriteByte((u8)ReadAddr);   for(i=0;i<NumByteToRead;i++){ pBuffer[i]=SPI2_ReadWriteByte(0XFF);       //循环读数  }W25QXX_CS=1;
}

void FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{W25_CS_ENABLE();
W25_RW_Byte(W25X_ReadData);
W25_RW_Byte((ReadAddr & 0xFF0000) >> 16);
W25_RW_Byte((ReadAddr& 0xFF00) >> 8);
W25_RW_Byte(ReadAddr & 0xFF);
while (NumByteToRead--) { *pBuffer = W25_RW_Byte(Dummy_Byte);
pBuffer++; }
W25_CS_DISABLE(); }

(7)页写函数(0x02)(只能按页写数据):

void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{u16 i;  W25QXX_Write_Enable();                     //SET WEL W25QXX_CS=0;                             //使能器件   SPI2_ReadWriteByte(W25X_PageProgram);          //发送写页命令   SPI2_ReadWriteByte((u8)((WriteAddr)>>16));     //发送24bit地址    SPI2_ReadWriteByte((u8)((WriteAddr)>>8));   SPI2_ReadWriteByte((u8)WriteAddr);   for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循环写数  W25QXX_CS=1;                               //取消片选 W25QXX_Wait_Busy();                          //等待写入结束
}

void FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{Flash_WritenEN();
W25_CS_ENABLE();
W25_RW_Byte(W25X_PageProgram);
W25_RW_Byte((WriteAddr & 0xFF0000) >> 16);
W25_RW_Byte((WriteAddr & 0xFF00) >> 8);
W25_RW_Byte(WriteAddr & 0xFF);
if(NumByteToWrite > SPI_FLASH_PerWritePageSize) {NumByteToWrite = SPI_FLASH_PerWritePageSize; }
while (NumByteToWrite--) {W25_RW_Byte(*pBuffer);
pBuffer++; }
W25_CS_DISABLE();
FLASH_WaitForWriteEnd(); }

(8)擦除整个芯片(0xC7),擦除一个扇区(0x20):

//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{                                   W25QXX_Write_Enable();                      //SET WEL W25QXX_Wait_Busy();   W25QXX_CS=0;                               //使能器件   SPI2_ReadWriteByte(W25X_ChipErase);            //发送片擦除命令  W25QXX_CS=1;                                //取消片选            W25QXX_Wait_Busy();                           //等待芯片擦除结束
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)
{  //监视falsh擦除情况,测试用   printf("fe:%x\r\n",Dst_Addr);    Dst_Addr*=4096;W25QXX_Write_Enable();                    //SET WEL    W25QXX_Wait_Busy();   W25QXX_CS=0;                                //使能器件   SPI2_ReadWriteByte(W25X_SectorErase);          //发送扇区擦除指令 SPI2_ReadWriteByte((u8)((Dst_Addr)>>16));      //发送24bit地址    SPI2_ReadWriteByte((u8)((Dst_Addr)>>8));   SPI2_ReadWriteByte((u8)Dst_Addr);  W25QXX_CS=1;                                //取消片选            W25QXX_Wait_Busy();                           //等待擦除完成
}

(9)无检验页写SPI FLASH 函数:

void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{                    u16 pageremain;       pageremain=256-WriteAddr%256; //单页剩余的字节数                if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节while(1){      W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);if(NumByteToWrite==pageremain)break;//写入结束了else //NumByteToWrite>pageremain{pBuffer+=pageremain;WriteAddr+=pageremain;  NumByteToWrite-=pageremain;              //减去已经写入了的字节数if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节else pageremain=NumByteToWrite;       //不够256个字节了}};
}

(10)检验是否擦除写SPI FLASH 函数:

u8 W25QXX_BUFFER[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;//扇区剩余空间大小   //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用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++;//扇区地址增1secoff=0;//偏移位置为0     pBuffer+=secremain;                  //指针偏移WriteAddr+=secremain;               //写地址偏移    NumByteToWrite-=secremain;          //字节数递减if(NumByteToWrite>4096)secremain=4096;//下一个扇区还是写不完else secremain=NumByteToWrite;        //下一个扇区可以写完了}    };
}

其他W25Q128FLASH指令请参考flash数据手册

四、完整程序:

(1)spi.h:

#ifndef __SPI_H
#define __SPI_H
#include "sys.h"
void SPI2_Init(void);            //初始化SPI口
void SPI2_SetSpeed(u8 SpeedSet); //设置SPI速度
u8 SPI2_ReadWriteByte(u8 TxData);//SPI总线读写一个字节#endif

(2)spi.c

#include "spi.h"
void SPI2_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;SPI_InitTypeDef  SPI_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能 RCC_APB1PeriphClockCmd(  RCC_APB1Periph_SPI2,  ENABLE );//SPI2时钟使能   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PB13/14/15复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOBGPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);  //PB13/14/15上拉SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工SPI_InitStructure.SPI_Mode = SPI_Mode_Master;       //设置SPI工作模式:设置为主SPISPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;       //设置SPI的数据大小:SPI发送接收8位帧结构SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;       //串行同步时钟的空闲状态为高电平SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;  //串行同步时钟的第二个跳变沿(上升或下降)数据被采样SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;     //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;     //定义波特率预分频的值:波特率预分频值为256SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始SPI_InitStructure.SPI_CRCPolynomial = 7;  //CRC值计算的多项式SPI_Init(SPI2, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器SPI_Cmd(SPI2, ENABLE); //使能SPI外设SPI2_ReadWriteByte(0xff);//启动传输         }
//SPI 速度设置函数
//SpeedSet:
//SPI_BaudRatePrescaler_2   2分频
//SPI_BaudRatePrescaler_8   8分频
//SPI_BaudRatePrescaler_16  16分频
//SPI_BaudRatePrescaler_256 256分频 void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));SPI2->CR1&=0XFFC7;SPI2->CR1|=SPI_BaudRatePrescaler; //设置SPI2速度 SPI_Cmd(SPI2,ENABLE); } //SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{       u8 retry=0;                    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位{retry++;if(retry>200)return 0;}              SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据retry=0;while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位{retry++;if(retry>200)return 0;}                               return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}

(3)w25qxx.h:

#ifndef __FLASH_H
#define __FLASH_H
#include "sys.h"
//W25X系列/Q系列芯片列表
//W25Q80  ID  0XEF13
//W25Q16  ID  0XEF14
//W25Q32  ID  0XEF15
//W25Q64  ID  0XEF16
//W25Q128 ID  0XEF17
#define W25Q80  0XEF13
#define W25Q16  0XEF14
#define W25Q32  0XEF15
#define W25Q64  0XEF16
#define W25Q128 0XEF17extern u16 W25QXX_TYPE;                   //定义W25QXX芯片型号         #define  W25QXX_CS       PBout(12)       //W25QXX的片选信号//指令表
#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
#define W25X_ManufactDeviceID   0x90
#define W25X_JedecDeviceID      0x9F void W25QXX_Init(void);
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);
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

(4)25qxx.c:

#include "w25qxx.h"
#include "spi.h"
#include "delay.h"
#include "usart.h"
u16 W25QXX_TYPE=W25Q128;   //默认是W25Q128//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q128
//容量为16M字节,共有128个Block,4096个Sector //初始化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 推挽 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);W25QXX_CS=1;               //SPI FLASH不选中SPI2_Init();          //初始化SPISPI2_SetSpeed(SPI_BaudRatePrescaler_2);//设置为18M时钟,高速模式W25QXX_TYPE=W25QXX_ReadID();//读取FLASH ID.  }  //读取W25QXX的状态寄存器
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
u8 W25QXX_ReadSR(void)
{  u8 byte=0;   W25QXX_CS=0;                            //使能器件   SPI2_ReadWriteByte(W25X_ReadStatusReg); //发送读取状态寄存器命令    byte=SPI2_ReadWriteByte(0Xff);          //读取一个字节  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);//发送写取状态寄存器命令    SPI2_ReadWriteByte(sr);                  //写入一个字节  W25QXX_CS=1;                            //取消片选
}
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{W25QXX_CS=0;                              //使能器件   SPI2_ReadWriteByte(W25X_WriteEnable);  //发送写使能  W25QXX_CS=1;                              //取消片选
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{  W25QXX_CS=0;                            //使能器件   SPI2_ReadWriteByte(W25X_WriteDisable);  //发送写禁止指令    W25QXX_CS=1;                            //取消片选
}
//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
u16 W25QXX_ReadID(void)
{u16 Temp = 0;   W25QXX_CS=0;                 SPI2_ReadWriteByte(0x90);//发送读取ID命令     SPI2_ReadWriteByte(0x00);       SPI2_ReadWriteByte(0x00);       SPI2_ReadWriteByte(0x00);                  Temp|=SPI2_ReadWriteByte(0xFF)<<8;  Temp|=SPI2_ReadWriteByte(0xFF);   W25QXX_CS=1;                  return Temp;
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{ u16 i;                                            W25QXX_CS=0;                               //使能器件   SPI2_ReadWriteByte(W25X_ReadData);             //发送读取命令   SPI2_ReadWriteByte((u8)((ReadAddr)>>16));      //发送24bit地址    SPI2_ReadWriteByte((u8)((ReadAddr)>>8));   SPI2_ReadWriteByte((u8)ReadAddr);   for(i=0;i<NumByteToRead;i++){ pBuffer[i]=SPI2_ReadWriteByte(0XFF);       //循环读数  }W25QXX_CS=1;
}
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大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);          //发送写页命令   SPI2_ReadWriteByte((u8)((WriteAddr)>>16));     //发送24bit地址    SPI2_ReadWriteByte((u8)((WriteAddr)>>8));   SPI2_ReadWriteByte((u8)WriteAddr);   for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循环写数  W25QXX_CS=1;                               //取消片选 W25QXX_Wait_Busy();                          //等待写入结束
}
//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{                    u16 pageremain;       pageremain=256-WriteAddr%256; //单页剩余的字节数                if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节while(1){      W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);if(NumByteToWrite==pageremain)break;//写入结束了else //NumByteToWrite>pageremain{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];
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;//扇区剩余空间大小   //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用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++;//扇区地址增1secoff=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);            //发送片擦除命令  W25QXX_CS=1;                                //取消片选            W25QXX_Wait_Busy();                           //等待芯片擦除结束
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)
{  //监视falsh擦除情况,测试用   printf("fe:%x\r\n",Dst_Addr);    Dst_Addr*=4096;W25QXX_Write_Enable();                    //SET WEL    W25QXX_Wait_Busy();   W25QXX_CS=0;                                //使能器件   SPI2_ReadWriteByte(W25X_SectorErase);          //发送扇区擦除指令 SPI2_ReadWriteByte((u8)((Dst_Addr)>>16));      //发送24bit地址    SPI2_ReadWriteByte((u8)((Dst_Addr)>>8));   SPI2_ReadWriteByte((u8)Dst_Addr);  W25QXX_CS=1;                                //取消片选            W25QXX_Wait_Busy();                           //等待擦除完成
}
//等待空闲
void W25QXX_Wait_Busy(void)
{   while((W25QXX_ReadSR()&0x01)==0x01);          // 等待BUSY位清空
}
//进入掉电模式
void W25QXX_PowerDown(void)
{ W25QXX_CS=0;                                 //使能器件   SPI2_ReadWriteByte(W25X_PowerDown);        //发送掉电命令  W25QXX_CS=1;                             //取消片选            delay_us(3);                               //等待TPD
}
//唤醒
void W25QXX_WAKEUP(void)
{  W25QXX_CS=0;                                //使能器件   SPI2_ReadWriteByte(W25X_ReleasePowerDown); //  send W25X_PowerDown command 0xAB    W25QXX_CS=1;                               //取消片选            delay_us(3);                              //等待TRES1
}   

嵌入式单片机基础篇(三十六)之STM32F1SPI标准接口程序以及模拟SPI标准接口程序讲解相关推荐

  1. 嵌入式单片机基础篇(十)之Systick定时器原理及应用

    Systick定时器原理及应用 1.什么是Systick定时器? 回答: (1)ystick定时器,是一个简单的定时器,对于CM3,CM4内核芯片,都有Systick定时器. (2)Systick定时 ...

  2. 嵌入式单片机基础篇(一)之stm32F1GPIO详解

    嵌入式单片机基础篇(一)之stm32F1GPIO详解 stm32F1GPIO详解 下面以stm32f103zet6以及51单片机为例,讲解单片机最基本部分IO口 第一部分:stm32 STM32 的 ...

  3. WF4.0 基础篇 (三十 完) 对学习WF的一点建议

    从09年10月份 NET4.0 Bata 2发布后,我就开始写[WF4.0 基础篇]这个系列,经历了5个多月终于在NET4.0正式发布前将这个系列完成了 这段时间与一些对WF4.0感兴趣的公司与开发人 ...

  4. 嵌入式单片机基础篇(八)之两只看门狗

    嵌入式单片机基础篇(八)之两只看门狗 独立看门狗与窗口看门狗 第一部分:stm32 .STM32 内部自带了 2 个看门狗:独立看门狗(IWDG)和窗口看门狗(WWDG). (一)独立看门狗 STM3 ...

  5. 鸟哥的Linux私房菜(基础篇)- 第二十六章、Linux 核心编译与管理

    第二十六章.Linux核心编译与管理 最近升级日期:2009/09/18 我们说的 Linux 其实指的就是核心 (kernel) 而已.这个核心控制你主机的所有硬件并提供系统所有的功能,所以说,他重 ...

  6. 鸟哥的Linux私房菜(基础篇)- 第十六章、例行性工作排程 (crontab)

    第十六章.例行性工作排程 (crontab) 最近升级日期:2009/09/11 学习了基础篇也一阵子了,你会发现到为什么系统常常会主动的进行一些任务?这些任务到底是谁在配置工作的?如果你想要让自己设 ...

  7. python进阶记录之基础篇二十六_Python进阶记录之基础篇(十六)

    回顾 在Python进阶记录之基础篇(十五)中,我们介绍了面向对象的基本概念以及Python中类和对象的基础知识,需要重点掌握类的创建和对象的使用.今天我们继续讲一下Python中面向对象的相关知识点 ...

  8. 嵌入式单片机基础篇(三十九)之CAN通讯以及STM32F1单片机程序

    CAN通讯以及STM32F1单片机程序 1.CAN通讯基本概念: (1)定义: CAN是Controller Area Network 的缩写(以下称为CAN),是ISO国际标准化的串行通信协议.由德 ...

  9. 嵌入式单片机基础篇(二十)之FSMC

    FSMC详解 1.FSMC简介: FSMC,即灵活的静态存储控制器,能够与同步或异步存储器和 16 位 PC 存储器卡连接,STM32 的 FSMC 接口支持包括 SRAM.NAND FLASH.NO ...

最新文章

  1. 关于使用AIR开发移动APP 的技术解决方案 笔记
  2. 【设计模式】外观模式 ( 概念 | 适用场景 | 优缺点 | 代码示例 )
  3. python 数据导入mysql_用python批量向数据库(MySQL)中导入数据
  4. Asp.Net Core 发布到IIS
  5. 三级分类菜单的数据库设计
  6. 网管分析数据包? 组合一下吧。
  7. PAT 00-自测1. 打印沙漏(20)
  8. 你有什么道理后悔没有早点知道?
  9. 远程命令行添加(删除)注册表键值(远程打开)
  10. js小游戏之锅打灰太狼
  11. proteus仿真Arduino
  12. python企业微信登录_python 微信企业号
  13. 裁判文书网数据采集爬虫2021-08
  14. DVM和ART的区别详解
  15. java程序设计实用教程_清华大学出版社-图书详情-《Java程序设计实用教程》
  16. LD3320语音识别模块分析
  17. eos智能合约执行流程
  18. Ubuntu安装配置谷歌拼音输入法
  19. 2021年华数杯数学建模A题电动汽车无线充电优化匹配研究求解全过程文档及程序
  20. netgear 网件路由器怎么了

热门文章

  1. 浅谈async函数await用法
  2. 动画 | 什么是二叉堆?
  3. Redis数据持久化机制及数据恢复
  4. Android用kotlin修改一个图片编辑库做一个作业点评的功能
  5. docker 运行指定内存
  6. 关于System.Data.ParameterDirection四个枚举类型所起的作用
  7. 当代家庭教育杂志当代家庭教育杂志社当代家庭教育编辑部2022年第31期目录
  8. rootsu被占用,su root 严重错误
  9. GoodNotes5下载之使用教程
  10. 方舟服务器在线人数查询软件,Steam游戏该如何保持在线人数?方舟生存进化告诉你答案!...