SPI通信

目录

一.软件简介

二.时序​编辑

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

四.代码实现

(1)软件模拟SPI

(2)硬件实现SPI


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功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值