7. SPI通信

SPI传输方式

采用同步方式(Synchronous)传输数据

Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的。

typedef struct
{
    uint16_t SPI_Direction; // 设置SPI 的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式
   uint16_t SPI_Mode; // 设置SPI 的主从模式
   uint16_t SPI_DataSize; // 为8 位还是16 位帧格式选择项
   uint16_t SPI_CPOL; // 设置时钟极性
   uint16_t SPI_CPHA; // 设置时钟相位
   uint16_t SPI_NSS;   //设置NSS 信号由硬件(NSS管脚)还是软件控制
   uint16_t SPI_BaudRatePrescaler;  //设置SPI 波特率预分频值
   uint16_t SPI_FirstBit;    //设置数据传输顺序是MSB 位在前还是LSB 位在前
   uint16_t SPI_CRCPolynomial; //设置CRC 校验多项式,提高通信可靠性,大于1 即可
}SPI_InitTypeDef;
void SPI1_Configuration(void)
{
 	GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PA5/6/7复用推挽输出 //主从模式决定数据方向
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA
 	GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);  //PA5/6/7上拉

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;		//串行同步时钟的空闲状态为低电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;	//串行同步时钟的第一个跳变沿(上升或下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;		//定义波特率预分频的值:波特率预分频值为2
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI1, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
	
	SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);
	SPI_Cmd(SPI1, ENABLE); //使能SPI外设
} 
void SPI2_Configuration(void)
{
 	GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PB13/14/15复用推挽输出 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
 	GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);  //PB13/14/15上拉

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;		//设置SPI工作模式:设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;		//串行同步时钟的空闲状态为低电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;	//串行同步时钟的第1个跳变沿(上升或下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;		//定义波特率预分频的值:波特率预分频值为2
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
 
	SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,ENABLE);
	SPI_Cmd(SPI2, ENABLE); //使能SPI外设
}
void SPI1_IRQHandler(void)
{
  if(SPI_I2S_GetITStatus(SPI1,SPI_I2S_IT_RXNE) !=RESET)//中断标志位对应中断的Get
	{
		Buffer1_Rx[RxCNT1++] = SPI_I2S_ReceiveData(SPI1);//清除标志位,RXNE=0
		if(RxCNT1 == buffer_size)
		{
			SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,DISABLE);	
		}
	}
}


void SPI2_IRQHandler(void)
{
	 if(SPI_I2S_GetITStatus(SPI2,SPI_I2S_IT_RXNE) !=RESET)//中断标志位对应中断的Get
	{
		Buffer2_Rx[RxCNT2++] = SPI_I2S_ReceiveData(SPI2);//清除标志位,RXNE=0
		if(RxCNT2 == buffer_size)
		{
			SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,DISABLE);	
		}
	}
}

模拟时序

很典型的一个写8bit数据的函数,入口数据dat,首先选中LCD,进入循环,对dat拆出高位bit分析是1还是0,再决定MOSI输出的是1还是0,并且这个过程是在一个时钟脉冲内完成的。一次循环结束后,dat左移一位,将低一位的bit推向高位,为下一次循环做好准备。在8次循环过后,就完成了一个8bit数据的发送。其实LCD的使用中,SPI相关的只有以下函数,其他的都是在此之上进行的扩展。(没有明显区分采样时刻是奇数还是偶数边沿)
 

void LCD_Writ_Bus(u8 dat) 
{	
	u8 i;
	LCD_CS_Clr();
	for(i=0;i<8;i++)
	{			  
		LCD_SCLK_Clr();
		if(dat&0x80)
		{
		   LCD_MOSI_1();
		}
		else
		{
		   LCD_MOSI_0();
		}
		LCD_SCLK_Set();
		dat<<=1;
	}	
  LCD_CS_Set();	
}

在SPI通信中,MISO信号线不需要单独设置为输入管脚。这是因为在主模式下,MISO信号线的输入模式是由SPI控制器自动配置的。当主设备向从设备发送数据时,从设备会将数据通过MISO信号线返回给主设备,此时SPI控制器会自动将MISO信号线的输入模式设置为输入模式,以读取从设备返回的数据。而在从模式下,MISO信号线的输入模式也是由SPI控制器自动配置的,从设备只需要将数据通过MISO信号线发送给主设备即可。

#define SPI_Direction_2Lines_FullDuplex ((uint16_t)0x0000)
#define SPI_Direction_2Lines_RxOnly     ((uint16_t)0x0400)
#define SPI_Direction_1Line_Rx          ((uint16_t)0x8000)
#define SPI_Direction_1Line_Tx          ((uint16_t)0xC000)

void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)
{
  assert_param(IS_SPI_ALL_PERIPH(SPIx));/*检查参数*/
  SPIx->DR = Data;/*在DR寄存器中写入要发送的数据*/
}

uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)
{
  assert_param(IS_SPI_ALL_PERIPH(SPIx));/*检查参数*/
  return SPIx->DR;/*返回DR寄存器中的数据*/
}
#define SPI_I2S_FLAG_RXNE               ((uint16_t)0x0001)
#define SPI_I2S_FLAG_TXE                ((uint16_t)0x0002)

#define SPI_I2S_IT_TXE                  ((uint8_t)0x71)
#define SPI_I2S_IT_RXNE                 ((uint8_t)0x60)
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
	while (TxCNT2 < buffer_size)
	{
		while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);	//等待发完后置1(上一次)
		SPI_I2S_SendData(SPI2,Buffer2_Tx[TxCNT2++]);//无时钟SCK输出,TDR2—>TSR2---x->MOSI(PB14)
		//while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);//若为主设备时该句可放后面
		
		while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);	//等待SPI1发送完置1
		SPI_I2S_SendData(SPI1,Buffer1_Tx[TxCNT1++]);//有时钟SCK输出,TDR1—>TSR1---x->MOSI(PA7)
        //与此同时,TSR2->MOSI(PB14)
		
		SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);	
		SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,ENABLE);
	}

查询时 

	while (RxCNT2 < buffer_size)
	{
		while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);	//等待发完后置1(上一次)
		SPI_I2S_SendData(SPI2,Buffer2_Tx[TxCNT2++]);//无时钟SCK输出,TDR2—>TSR2---x->MOSI(PB14)
		//while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);//若为主设备时该句可放后面
		
		while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);	//等待SPI1发送完置1
		SPI_I2S_SendData(SPI1,Buffer1_Tx[TxCNT1++]);//有时钟SCK输出,TDR1—>TSR1---x->MOSI(PA7)
        //与此同时,TSR2->MOSI(PB14)
		
		while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);	//等待SPI1接受完置1
		Buffer1_Rx[RxCNT1++] = SPI_I2S_ReceiveData(SPI1);//清除标志位,RXNE=0
		while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);	//等待SPI2接收完置1
		Buffer2_Rx[RxCNT2++] = SPI_I2S_ReceiveData(SPI2);//清除标志位,RXNE=0
	}
	/**********配置RCC时钟,打开对应端口的时钟**********/
		RCC_APB2PeriphClockCmd(	RCC_APB2Periph_SPI1,  ENABLE );//SPI2时钟使能
		RCC_APB1PeriphClockCmd(	RCC_APB1Periph_SPI2,  ENABLE );//SPI2时钟使能
		RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE);
		RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能
/***************NVIC中断优先级配置程序********************/
void NVIC_Configuration(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	
	/*调用时记得选择分组,要么在main里加,要么在这里加,配置一次即可*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	
/**************用哪个加哪个,加在后面就行***********************/
	NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn;			//对应的中断向量通道//在stm32f10x.h中可以找到对应的外部通道名字
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;//抢占优先级1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;	 	//子优先级1
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);							//根据指定的参数初始化NVIC寄存器
}

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值