SPI通信(STM32)

一、SPI通信

1、SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线

2、四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master I3、nput Slave Output)、SS(Slave Select) 同步,全双工 支持总线挂载多设备(一主多从)

二、硬件电路

1.所有SPI设备的SCK、MOSI、MISO分别连在一起

2.主机另外引出多条SS控制线,分别接到各从机的SS引脚

3.输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入

4.片选信号SS是低电平有效,哪个SS被置为低电平,总线上的设备就哪个起作用,有且至多    仅 有 一个从设备与主机通信

PS:

1、推挽输出均有很强的驱动能力,使得上升沿和下降沿的速度很快。

2、SS有高电平时(从机未被选中),此时从机的MISO切换成高阻态。

三、移位示意图

例如:主机的数据要发送到从机,从机的数据要发送主机(采用模式1)

1、时钟产生一个上升沿的电平,主机的高位移到MOSI(移位寄存器)线上,从机的高位移到MISO(移位寄存器)上。(SPI高位先行)

2、时钟产生一个下降沿时,主机会对MISO线上(移位寄存器)进行采样,从机会对MOSI线上(移位寄存器)进行采样

3、时钟继续产生一个上升沿的电平,主机的高位移到MOSI线上(移位寄存器),从机的高位移到MISO上(移位寄存器)。(SPI高位先行)

4、时钟继续产生一个下降沿时,主机会对MISO线上(移位寄存器)进行采样,从机会对MOSI线上(移位寄存器)进行采样

5、这样不断的循环直到主机把从机的数据置换过来

SPI通信最终的原理是主机与从机的字节交换,当主机没有数据给从机(主机只接收),而主机又需要从机的数据,主机就可以写一个垃圾数据(0XFF、0X00)给从机,就可以把主机想要的从机数据置换过来。当主机只发送的时候,从机也会随便写一个垃圾数据(0XFF、0XFF)给主机,将主机的数据置换出去

四、SPI时序

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

终止条件:SS从低电平切换到高电平

交换一个字节(模式0)

CPOL=0:空闲状态时,SCK为低电平

CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

由于SCK第一个边沿就要采样数据,那么从机和主机应该提前把数据移出来,所以在SS下降沿的时候主机和从机就移出数据了,如果一个字节交换结束之后还要继续交换数据,SCK的最后一个下降沿就会移出第二字节的最高位数据,如果主机和从机不继续交换数据,主机和从机的最高位也会冒一个头

交换一个字节(模式1)

CPOL=0:空闲状态时,SCK为低电平

CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

交换一个字节(模式2)

CPOL=1:空闲状态时,SCK为高电平

CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

交换一个字节(模式3)

CPOL=1:空闲状态时,SCK为高电平

CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

五、硬件SPI代码

#include "stm32f10x.h"                  // Device header
#include "mySPI.h"

//PA3 RTS
/**
  * 函    数:SPI写SS引脚电平,SS仍由软件模拟
  * 参    数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
  */
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);		//根据BitValue,设置SS引脚的电平
}
/**
  * 函    数:RST由软件模拟
  * 参    数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置RST为低电平,当BitValue为1时,需要置RST为高电平
  */
void SPI_RST(uint8_t number)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_3,(BitAction)number);
}

/**
  * 函    数:SPI初始化
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);	//开启SPI1的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//SPI1 NSS/SDA
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA4引脚初始化为推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;	//SPI1 MOSI/--7
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//SPI1 SCK/--5
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA5和PA7引脚初始化为复用推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//SPI1 MISO/
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA6引脚初始化为上拉输入
	//PA3是RC522的复位引脚
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;				 //
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 ////PA3 RTS
	GPIO_Init(GPIOA, &GPIO_InitStructure);					 //
	
	/*SPI初始化*/
	SPI_InitTypeDef SPI_InitStructure;						//定义结构体变量
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//模式,选择为SPI主模式
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//方向,选择2线全双工
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据宽度,选择为8位
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;		//先行位,选择高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率分频,选择128分频
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;				//SPI极性,选择低极性
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;			//SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;				//NSS,选择由软件控制
	SPI_InitStructure.SPI_CRCPolynomial = 7;				//CRC多项式,暂时用不到,给默认值7
	SPI_Init(SPI1, &SPI_InitStructure);						//将结构体变量交给SPI_Init,配置SPI1
	
	/*SPI使能*/
	SPI_Cmd(SPI1, ENABLE);									//使能SPI1,开始运行
	
	/*设置默认电平*/
	MySPI_W_SS(1);											//SS默认高电平
	SPI_RST(1);
}

/**
  * 函    数:SPI起始
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Start(void)
{
	MySPI_W_SS(0);				//拉低SS,开始时序
}

/**
  * 函    数:SPI终止
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Stop(void)
{
	MySPI_W_SS(1);				//拉高SS,终止时序
}

/**
  * 函    数:SPI交换传输一个字节,使用SPI模式0
  * 参    数:ByteSend 要发送的一个字节
  * 返 回 值:接收的一个字节
  */
uint8_t MySPI_RW_Byte(uint8_t ByteSend)
{
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);	//等待发送数据寄存器空
	
	SPI_I2S_SendData(SPI1, ByteSend);								//写入数据到发送数据寄存器,开始产生时序
	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);	//等待接收数据寄存器非空
	
	return SPI_I2S_ReceiveData(SPI1);								//读取接收到的数据并返回
}

六、软件SPI代码

#include "stm32f10x.h"
#include "softspi.h"


/**********************************

*该程序是软件SPI的测试 用SPI零模式来操作
*SS---PA4
*SCLK---PA5
*PA6--MISO
*PA7--MOSI

*对于主机来说片选信号和时钟和主机输入都是输出引脚
*输出引脚配置为推挽输出(有独立的输入输出线,不需要考虑输入扫描时,输入线有输出的电平造成短路现象),输入引脚配置为浮空或上拉输入

***********************************/
//片选信号写操作
void MySPI_W_SS(uint8_t value)
{
	 GPIO_WriteBit(A, Pin_SS, (BitAction)value);
}
//主机输出从机输入写操作
void MySPI_W_MOSI(uint8_t value)
{
	 GPIO_WriteBit(A, Pin_MOSI, (BitAction)value);
}
//时钟信号写操作
void MySPI_W_SCLK(uint8_t value)
{
	 GPIO_WriteBit(A, Pin_SCLK, (BitAction)value);
}
//读取MISO操作
uint8_t MySPI_R_MISO(void)
{
	 return GPIO_ReadInputDataBit(A,  Pin_MISO);
}



//初始化SPI引脚
void MySoftSPI_Init(void)
{
	GPIO_InitTypeDef Init_GPIO; 

	//初始化时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

	//初始化输出引脚
	Init_GPIO.GPIO_Pin = Pin_SS | Pin_SCLK | Pin_MOSI;		//设置引脚
	Init_GPIO.GPIO_Speed = GPIO_Speed_50MHz;	//设置输出的速度
	Init_GPIO.GPIO_Mode = GPIO_Mode_Out_PP;	//设置输出模式为推挽输出
	GPIO_Init(A, &Init_GPIO);//真正的初始化到引脚结构体中
	
	//初始化输入引脚
	Init_GPIO.GPIO_Pin = Pin_MISO;		//设置引脚
	Init_GPIO.GPIO_Speed = GPIO_Speed_50MHz;	//设置输出的速度
	Init_GPIO.GPIO_Mode = GPIO_Mode_IPU;	    //设置输入模式为上拉输入
	GPIO_Init(A, &Init_GPIO);//真正的初始化到引脚结构体中

	MySPI_W_SS(1);//片选拉高
	//SoftW25Q64_W_MOSI(Bit_SET);//没有明确规定 可以不需理会
	MySPI_W_SCLK(0);//0模式初始化是低电平
}


//SPI起始信号
void MySPI_Start(void)
{
	MySPI_W_SS(0);//片选拉低
}

//SPI停止信号
void MySPI_Stop(void)
{
	MySPI_W_SS(1);//片选拉高
}

//SPI字节交换(传输数据),0模式
uint8_t MySPI_RW_Byte(uint8_t byte)
{
	uint8_t i,ReciveData = 0x00;
	for(i = 0;i < 8;i++)
	{
		MySPI_W_MOSI(byte & (0x80 >>i));//SS拉低之后把byte的每一位依次传到MOSI上(MOSI引脚上输出对应的电平)
		MySPI_W_SCLK(1);//这时自动将MOSI数据进行采样

		if(MySPI_R_MISO() == 1){ReciveData |= (0x80>>i);}//MISO上的引脚进行读取电平
		
		MySPI_W_SCLK(0);//传出下一位到MOSI上
	}
	return ReciveData;
}

SPI有连续输出和非连续输出,一般采用非连续输出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值