SPI通信协议(软件SPI和硬件SPI)

今天介绍一种新的通信协议——SPI,相比于之前学习的I2C,SPI通信的速率更快,相对而言,需要的资源就比较多,SPI通信需要六根线:四根通信线,SCK(时钟线),MOSI(主机输出从机输入),MISO(主机输入从机输出),SS(从机选择线),然VCC,GND;由于拥有时钟线所以采用同步时序,有两条数据传输线(I2C只有一条SDA)所以采用全双工;与I2C类似,支持挂载多个设备;SPI相比于I2C的好处是通信速率快,且时序简单易于学习,不足之处就是资源浪费

硬件电路要求:所有SPI通信设备的SCK,MOSI,MISO分别连在一起;主机的多条SS引脚分别接在从机的SS引脚上,SS引脚低电平代表主机开始跟从机通信了(主机一次只能选择一个从机进行通信),SS高电平代表主机结束通信;输出引脚(SCK,MOSI)配置为推挽输出(推挽输出:高低电平均有很强的驱动能力,使得SPI的上升沿与下降沿都十分迅速,从而通信速率得到提高),输入引脚(MISO)配置为浮空或上拉输入;

SPI交换字节移位示意图

当时钟的上升沿的时候移位寄存器的高位将数据放到数据线上(主机数据放MOSI,从机数据放MISO),时钟的下降沿将数据线中的数据转移到移位寄存器的低位,过程重复八次,达到交换一个字节的目的

待交换的数据:(主机发送0xAA,从机发送0x55)

第一个时钟上升沿:(高位先行,所以向左移位)

第一个时钟下降沿:(在下降沿进行采样:将数据线上的数据转移到寄存器中)

第二个时钟上升沿

第二个时钟下降沿

直到第八个时钟,一个字节交换完毕,这就是SPI通信最基本的交换字节了,SPI的数据通信都是基于字节交换的,如果只想发送不想接收,那就不用在意从机发送过来的数据,直接执行一遍交换字节的时序,如果只想接收而不想发送,则可以随便发送一个数据(通常为0x00或0xFF),然后就接收到从机发过来的数据了;

SPI时序基本单元

起始条件:SS从高电平切换到低电平

终止条件:SS从低电平切换到高电平(SS在通信的过程中始终保持低电平)

SPI的四种模式:(通过CPOL,CPHA两个寄存器来配置SPI模式)

模式0:(MISO开始的一段中间线表示高阻态)

“SCK第一个边沿移入数据”,中的边沿不管是上升沿还是下降沿都可以;

由于第一个边沿移入数据(数据采样),所以在第0个边沿的时候就要移出数据,即在SS下降沿的时候就要移出数据了

其余模式类似模式0:

模式1:

模式2:

模式3:

软件SPI工程代码

写SCK,SS,MOSI,读MISO

void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

void MySPI_W_SCK(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}

void MySPI_W_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}

uint8_t MySPI_R_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}

软件SPI初始化

void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	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);
}

软件SPI交换字节

uint8_t MySPI_SwapByte(uint8_t ByteSend)//待发送的数据
{
	uint8_t i, ByteReceive = 0x00;
	
	for (i = 0; i < 8; i ++)
	{
		MySPI_W_MOSI(ByteSend & (0x80 >> i));
		MySPI_W_SCK(1);
		if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
		MySPI_W_SCK(0);
	}
	
	return ByteReceive;//接收的数据
}

利用SPI读写W25Q64

W25Q64指令集宏定义

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3

#define W25Q64_DUMMY_BYTE							0xFF

#endif

W25Q64写使能

void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

等待忙标志位(有超时判定)

void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	Timeout = 100000;
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

按页写(不能跨页写,如果跨页写入,则会直接回到页首)

void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//按页写指令
	MySPI_SwapByte(Address >> 16);//写入24位地址
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for (i = 0; i < Count; i ++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

按扇区擦除

void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);//按扇区擦除指令
	MySPI_SwapByte(Address >> 16);//写入24位地址
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

读数据

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);//读数据指令
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for (i = 0; i < Count; i ++)
	{
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}

读设备ID

void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_JEDEC_ID);//读ID指令
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//厂商ID
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//设备ID高八位
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);设备ID低八位
	MySPI_Stop();
}

硬件SPI

硬件SPI简介

SPI基本结构:发送数据时,先将数据放到发送数据寄存器TDR中,当移位寄存器中没有数据时,TDR中的数据转入移位寄存器中,置TXE标志位(TDR空标志位),接着时钟产生时序将移位寄存器中的数据发送出去;当发送完一个数据时,此时移位寄存器中也接收到了来自从机发送过来的数据,接着将数据转入接受数据寄存器RDR中,并置RXNE标志位(RDR非空标志位)

非连续传输数据(发送一个数据之后接收一个数据)

发送数据示意图

实例解读:当准备发数据时,如果检测到TXE=1(TDR为空),则软件写入数据(0xF1),此时TDR为0xF1,TXE则变为0,如果移位寄存器为空,则数据(0xF1)转入移位寄存器里,然后移位寄存器就将数据发送出去了,接着等待RXNE为1,即接收到数据,此时可以读取RDR里的数据,然后将下个数据(0xF2)写入TDR,接着重复以上过程,循环八次即可交换一个字节

非连续传输的缺点:在TXE置1后没有及时将下个数据写入TDR,而是等待RXNE置1后才写入下一个数据,导致传输速率变慢

硬件SPI工程代码:由于起始条件和停止条件都只是简单的写SS电平,而SS引脚又是用GPIO模拟,所以起始条件和停止条件的代码与软件SPI一样,这里不在重复了

硬件SPI初始化

void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;//GPIO模拟SS引脚配置为通用推挽输出
	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;//SCK,MOSI引脚配置为复用推挽输出
	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;//MISO引脚配置为上拉输入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	SPI_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//配置当前设备为主机
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//配置双线全双工模式
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//配置八位数据帧
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//配置高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//配置波特率预分频系数
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//配置时钟极性,默认低电平(模式0)
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//配置第一个边沿开始采样(模式0)
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//配置软件模拟SS引脚
	SPI_InitStructure.SPI_CRCPolynomial = 7;//配置CRR校验多项式
	SPI_Init(SPI1, &SPI_InitStructure);
	
	SPI_Cmd(SPI1, ENABLE);//使能SPI1
	
	MySPI_W_SS(1);//默认SS高电平
}

硬件交换字节

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);//等待TXE为1
	
	SPI_I2S_SendData(SPI1, ByteSend);//将数据写入TDR
	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);//等待RXNE为1
	
	return SPI_I2S_ReceiveData(SPI1);//读取从机数据
}

由于只是修改了通信底层代码,所以硬件SPI不需要重新写W25Q64代码,与软件相同

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值