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学习心得十四:串口通信相关知识及配置方法。
上一篇: Android学习之Fragment使用