目录
W25Q64,Flash存储芯片,内部可以存储8M字节的数据,掉电不丢失
SPI较I2C传输更快,速率取决于芯片厂商的设计需求
硬件开销比较大,通信线个数比较多,通信过程中会有资源浪费的现象
一.软件简介




器件的标号都是一样的


CPOL:极性选择,决定空闲状态SCK的默认电平
CPHA:时钟相位,并不是单独规定上升沿采样还是下降沿采样,而是决定第一个边沿采样还是第二个边沿快速采样,即“奇数边沿”或“偶数边沿”进行采样

因此SPI支持4种通讯模式

二.时序



指令码+读写数据的模型
在SPI从机的指令手册里都会对应好指令集



三.SPI外设通信(硬件SPI)

8位数据帧高位先行,通信一般以字节为单位
SPI的时钟是由PCLK分频而来的
APB2的PCLK是72MHz,挂载了SPI1
APB1的PCLK是36MHz,挂载了SPI2
I2S是一种音频传输协议,可以用于将音频数据传输到DAC(数模转换器)

NSS可以使用软件模拟来进行实现
TDR将数据写入移位寄存器,置TXE为1
移位寄存器将数据写入RDR,置RXNE为1





void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);//写DR数据寄存器
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);//读DR数据寄存器
void SPI_NSSInternalSoftwareConfig(SPI_TypeDef* SPIx, uint16_t SPI_NSSInternalSoft);//NSS引脚配置
void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);//8位或16位数据帧的配置
void SPI_BiDirectionalLineConfig(SPI_TypeDef* SPIx, uint16_t SPI_Direction);//半双工时,双向线的方向配置
四.代码实现
(1)软件模拟SPI
MySPI.c
#include "stm32f10x.h" // Device header
void MySPI_W_SS(uint8_t Value)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)Value);
}
void MySPI_W_SCK(uint8_t Value)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)Value);
}
void MySPI_W_MOSI(uint8_t Value)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)Value);
}
uint8_t MySPI_R_MISO_Byte(void)
{
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}
void MySPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//主机输出引脚位推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//主机输入引脚为上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
MySPI_W_SS(1);
MySPI_W_SCK(0);
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
uint8_t MySPI_SwapData(uint8_t ReturnData)//模式0
{
uint8_t i,ReceiveData = 0x00;//记得进行初始化
for(i=0;i<8;i++)
{
MySPI_W_MOSI(ReturnData & (0x80 >> i));//0x80 >> i 用于挑出数据的某一位或者某几位(用于屏蔽其他位数据),该类型的数据就称之为掩码,即高位先行
MySPI_W_SCK(1);
if(MySPI_R_MISO_Byte() == 1){ReceiveData |= (0x80 >> i);}//高位先行
MySPI_W_SCK(0);
}
return ReceiveData;
}
//下面的函数设计更加契合移位模型
//uint8_t MySPI_ReceiveData(uint8_t ReturnData)
//{
// uint8_t i;
// for(i=0;i<8;i++)
// {
//
// MySPI_W_MOSI(ReturnData & 0x80);
// ReturnData <<= 1;//移位模型,左移一位末尾自动补0,移出最高位,空出最低位
// MySPI_W_SCK(1);
// if(MySPI_R_MISO_Byte() == 1){ReturnData |= 0x01;}
// MySPI_W_SCK(0);
// return ReturnData;
// }
//}
W25Q64.c
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
void W25Q64_Init(void)
{
MySPI_Init();
}
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{
MySPI_Start();
MySPI_SwapData(W25Q64_JEDEC_ID);
*MID = MySPI_SwapData(W25Q64_DUMMY_BYTE);
*DID = MySPI_SwapData(W25Q64_DUMMY_BYTE);
*DID <<= 8;
*DID |= MySPI_SwapData(W25Q64_DUMMY_BYTE);
MySPI_Stop();
}
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapData(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}
void W25Q64_WAITBUSY(void)
{
uint32_t Timeout = 1000000;
MySPI_Start();
MySPI_SwapData(W25Q64_READ_STATUS_REGISTER_1);
//这里只读最低的busy位
while((MySPI_SwapData(W25Q64_DUMMY_BYTE) & 0x01) == 1)//可以连续一直读状态
{
Timeout --;
if(Timeout == 0)
{
break;
}
}
MySPI_Stop();
}
//这种Flag的写法不能一直读出busy的数据位
//void W25Q64_WAITBUSY(void)
//{
// uint32_t Timeout = 1000000;
// uint8_t Flag;
// MySPI_Start();
// MySPI_SwapData(W25Q64_READ_STATUS_REGISTER_1);
// Flag = (MySPI_SwapData(W25Q64_DUMMY_BYTE) & 0x01);//这里只读最低的busy位
// while(Flag == 1)
// {
// Timeout --;
// if(Timeout == 0)
// {
//
// break;
// }
// }
// MySPI_Stop();
//}
//c语言没有24位的数据类型,所以地址直接设置成32位既可
void W25Q64_Page_Program(uint32_t Address,uint8_t *DataArray,uint16_t count)//这里的count要设置为16位数据,页编程可以写0~256个数的数据,而8位的count只能写0~255
{
uint16_t i;
W25Q64_WriteEnable();//该函数在一条时序结束后会自动失能,会顺手关门
MySPI_Start();
MySPI_SwapData(W25Q64_PAGE_PROGRAM);
MySPI_SwapData(Address>>16);
MySPI_SwapData(Address>>8);
MySPI_SwapData(Address);
for(i = 0; i<count; i++)
{
MySPI_SwapData(DataArray[i]);//这里要将数据进行交换,也就是发送出去
}
MySPI_Stop();
W25Q64_WAITBUSY();//事后等待
}
//事前等待效率高,但每一个函数(写入+读取)都要加;事后等待最保险,只用在写操作函数等待即可
void W25Q64_Sector_Erase(uint32_t Address)
{
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapData(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapData(Address>>16);
MySPI_SwapData(Address>>8);
MySPI_SwapData(Address);
MySPI_Stop();
W25Q64_WAITBUSY();
}
void W25Q64_ReadData(uint32_t Address,uint8_t *DataReceiveArray,uint32_t count)//读取数据没有页限制,所以可以取得更大
{
uint32_t i;
MySPI_Start();
MySPI_SwapData(W25Q64_READ_DATA);
MySPI_SwapData(Address>>16);
MySPI_SwapData(Address>>8);
MySPI_SwapData(Address);
for(i = 0; i<count; i++)
{
DataReceiveArray[i] = MySPI_SwapData(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
uint8_t MID;
uint16_t DID;
uint8_t DataArray[] = {0x00,0x01,0x02,0x04};
uint8_t DataReceiveArray[4] = {0,0,0,0};
int main()
{
OLED_Init();
W25Q64_Init();
W25Q64_ReadID(&MID,&DID);
OLED_ShowString(1,1,"MID:");
OLED_ShowString(1,8,"DID:");
OLED_ShowString(2,1,"D:");
OLED_ShowString(3,1,"R:");
OLED_ShowHexNum(1,5,MID,2);
OLED_ShowHexNum(1,12,DID,4);
W25Q64_Sector_Erase(0x000000);
W25Q64_Page_Program(0x000000,DataArray,4);//写入数据不能跨页
W25Q64_ReadData(0x000000,DataReceiveArray,4);//读取数据可以跨页
OLED_ShowHexNum(2,3,DataArray[0],2);
OLED_ShowHexNum(2,6,DataArray[1],2);
OLED_ShowHexNum(2,9,DataArray[2],2);
OLED_ShowHexNum(2,12,DataArray[3],2);
OLED_ShowHexNum(3,3,DataReceiveArray[0],2);
OLED_ShowHexNum(3,6,DataReceiveArray[1],2);
OLED_ShowHexNum(3,9,DataReceiveArray[2],2);
OLED_ShowHexNum(3,12,DataReceiveArray[3],2);
while(1)
{
}
}
(2)硬件实现SPI
MySPI.c
#include "stm32f10x.h" // Device header
void MySPI_W_SS(uint8_t Value)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)Value);
}
void MySPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//主机输出引脚位推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//主机输入引脚为上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//主机输入引脚为复用推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;
SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;//第一个边沿开始采样,CPHA=0
SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;//设置SCK空闲默认低电平
SPI_InitStructure.SPI_CRCPolynomial=7;
SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;//8为数据帧
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//双线全双工,该参数配置SPI裁剪引脚
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
SPI_InitStructure.SPI_Mode=SPI_Mode_Master;//设置设备为主机或从机
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;//软件NSS
SPI_Init(SPI1,&SPI_InitStructure);
SPI_Cmd(SPI1,ENABLE);
MySPI_W_SS(1);//默认设置SCK为高电平,不选择为从机
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
uint8_t MySPI_SwapData(uint8_t ReturnData)//模式0
{
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) != SET);//TXE会在写入数据后自动清除
SPI_I2S_SendData(SPI1,ReturnData);//数据转移到移位寄存器,会自动生成波形
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) != SET);//RXNE会在读取数据后自动清除
return SPI_I2S_ReceiveData(SPI1);
}
W25Q64.c
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
void W25Q64_Init(void)
{
MySPI_Init();
}
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{
MySPI_Start();
MySPI_SwapData(W25Q64_JEDEC_ID);
*MID = MySPI_SwapData(W25Q64_DUMMY_BYTE);
*DID = MySPI_SwapData(W25Q64_DUMMY_BYTE);
*DID <<= 8;
*DID |= MySPI_SwapData(W25Q64_DUMMY_BYTE);
MySPI_Stop();
}
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapData(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}
void W25Q64_WAITBUSY(void)
{
uint32_t Timeout = 1000000;
MySPI_Start();
MySPI_SwapData(W25Q64_READ_STATUS_REGISTER_1);
//这里只读最低的busy位
while((MySPI_SwapData(W25Q64_DUMMY_BYTE) & 0x01) == 1)//可以连续一直读状态
{
Timeout --;
if(Timeout == 0)
{
break;
}
}
MySPI_Stop();
}
void W25Q64_Page_Program(uint32_t Address,uint8_t *DataArray,uint16_t count)//这里的count要设置为16位数据,页编程可以写0~256个数的数据,而8位的count只能写0~255
{
uint16_t i;
W25Q64_WriteEnable();//该函数在一条时序结束后会自动失能,会顺手关门
MySPI_Start();
MySPI_SwapData(W25Q64_PAGE_PROGRAM);
MySPI_SwapData(Address>>16);
MySPI_SwapData(Address>>8);
MySPI_SwapData(Address);
for(i = 0; i<count; i++)
{
MySPI_SwapData(DataArray[i]);//这里要将数据进行交换,也就是发送出去
}
MySPI_Stop();
W25Q64_WAITBUSY();//事后等待
}
void W25Q64_Sector_Erase(uint32_t Address)
{
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapData(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapData(Address>>16);
MySPI_SwapData(Address>>8);
MySPI_SwapData(Address);
MySPI_Stop();
W25Q64_WAITBUSY();
}
void W25Q64_ReadData(uint32_t Address,uint8_t *DataReceiveArray,uint32_t count)//读取数据没有页限制,所以可以取得更大
{
uint32_t i;
MySPI_Start();
MySPI_SwapData(W25Q64_READ_DATA);
MySPI_SwapData(Address>>16);
MySPI_SwapData(Address>>8);
MySPI_SwapData(Address);
for(i = 0; i<count; i++)
{
DataReceiveArray[i] = MySPI_SwapData(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
uint8_t MID;
uint16_t DID;
uint8_t DataArray[] = {0x00,0x01,0x02,0x04};
uint8_t DataReceiveArray[4] = {0,0,0,0};
int main()
{
OLED_Init();
W25Q64_Init();
W25Q64_ReadID(&MID,&DID);
OLED_ShowString(1,1,"MID:");
OLED_ShowString(1,8,"DID:");
OLED_ShowString(2,1,"D:");
OLED_ShowString(3,1,"R:");
OLED_ShowHexNum(1,5,MID,2);
OLED_ShowHexNum(1,12,DID,4);
W25Q64_Sector_Erase(0x000000);
W25Q64_Page_Program(0x000000,DataArray,4);//写入数据不能跨页
W25Q64_ReadData(0x000000,DataReceiveArray,4);//读取数据可以跨页
OLED_ShowHexNum(2,3,DataArray[0],2);
OLED_ShowHexNum(2,6,DataArray[1],2);
OLED_ShowHexNum(2,9,DataArray[2],2);
OLED_ShowHexNum(2,12,DataArray[3],2);
OLED_ShowHexNum(3,3,DataReceiveArray[0],2);
OLED_ShowHexNum(3,6,DataReceiveArray[1],2);
OLED_ShowHexNum(3,9,DataReceiveArray[2],2);
OLED_ShowHexNum(3,12,DataReceiveArray[3],2);
while(1)
{
}
}
注:

五.QSPI通讯协议



上图是QSPI传输Flash设备
QSPI可以去查看具体的手册
4线传输

QSPI也可以实现标准的SPI功能
1537

被折叠的 条评论
为什么被折叠?



