STM32 硬件 SPI 半双工三线的研究心得

a7105可以使用四线spi,或者3线spi, 但是之前都是使用3线的软件模拟的三线spi的,所以不想改其它代码了,就想可以提高一个spi的读写速度,原来软件方式的读写速度,在48Mhz的03x下面,大约速度是1.6mbs,使用硬件spi之后,最终大约速度为12mbs. 

     为了令它可以稳定工作,这个还是花了不了时间了。主要的挑战是因为是使用3线spi,情况有些特殊,我们需要使用MOSI一条数据线实现主和从的双向模式通信。根据文档,我们使用进行以下设置

1. SPIx->CR1 的 BIDIMODE 设置为1,表示启用双向模式

2. 使用SPIX->CR1的BIDIOE 来控制方向,0表示当前为Master读数据,1表示当前为Master写数据

3. 更换这个方向时需要把SPIx禁用, SPIX->CR1的SPE,来控制。

这个总体思路是这样的,我实际编写完代码,发现是完全不工作的,网上也比较少这样的使用,有少数几个文章有说这个。

根据文档,在BIDIMODE 为1,BIDIOE为0, 时钟CLK在SPE马上开启输出,同时它只会在SPE为零时才会停,这个意味着,我们要依赖于Slave的处理速度,当时也依赖而自己准确开关这个CLK信号,否则这个读出来的数据就会错乱。

        这时需要一个比较慢的速度才可以稳定,最开始我并没有注意到这个,在    spiInit.SPI_BaudRatePrescaler 小于 SPI_BaudRatePrescaler_128时,要不读取全是时,要不出现一开始是对的,读着读着,数据就是错的,最后读出来全零的情况。只能不断的降低读写速度。最后才能稳定无误。但是128分频后,明显比软件模拟的方式还慢,这个就没有价值了。这个代码如下:

void spi_init()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);


	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;


	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 |  GPIO_Pin_15;
	

	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_0); 
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_0);

	SPI_I2S_DeInit(SPI1);
	spiInit.SPI_Direction = SPI_Direction_1Line_Tx;
	spiInit.SPI_Mode = SPI_Mode_Master;
	spiInit.SPI_DataSize  = SPI_DataSize_8b;
	spiInit.SPI_CPOL  = SPI_CPOL_Low;
	spiInit.SPI_CPHA = SPI_CPHA_1Edge;
	spiInit.SPI_NSS = SPI_NSS_Soft;
	spiInit.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_128;
	spiInit.SPI_FirstBit  = SPI_FirstBit_MSB;
	spiInit.SPI_CRCPolynomial = 7;
	SPI_Init(SPI1, &spiInit);
	
	SPI_SSOutputCmd(SPI1,DISABLE);
	  
	//NSS
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	SPI_TIModeCmd(SPI1,DISABLE);
	SPI_NSSPulseModeCmd(SPI1,DISABLE);
	SPI_Cmd(SPI1,ENABLE);
}

void Rf_Spi_Write_Byte(uint8_t dat)
{
	if(!g_bisTx)
	{
		
		  SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
    	  SPI1->CR1 |= SPI_Direction_Tx;
    	g_bisTx=1;
	}

	 *(uint8_t*)&SPI1->DR = dat;
    SPI1->CR1 |= SPI_CR1_SPE; //ENABLE SPI


   // while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) != RESET){}
    //SPI_SendData8(SPI1,dat);
   
	while(!(SPI1->SR & SPI_I2S_FLAG_TXE)){}
	while (SPI1->SR &  SPI_I2S_FLAG_BSY){}
   
	 SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);
 


}

uint8_t Rf_Spi_Read_Byte(void)
{
#if USE_HARDWARE_SPI

	uint8_t dat =0;

	if(g_bisTx)
    {
	    SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
	    SPI1->CR1 &= SPI_Direction_Rx;
	     /* Clear FRXTH bit */
		SPI1->CR2 &= (uint16_t)~((uint16_t)SPI_CR2_FRXTH);
		/* Set new FRXTH bit value */
		SPI1->CR2 |= SPI_RxFIFOThreshold_QF;  //SET IT 8 BIT per READ, THAT is QUETER OF 32BIT
	    
	}
	SPI1->CR1 |= SPI_CR1_SPE; //ENABLE SPI
 	g_bisTx=0;

     while (!(SPI1->SR &  SPI_I2S_FLAG_RXNE)  ) ; // wait data received
     SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
	 dat = *(uint8_t*)&SPI1->DR;

	return dat;

}

        这个根据工作原理分析了一下,为什么在这样,主要是因为我们的读的时候,要是这个速度高,我们的mcu还没有去禁用SPI停时钟这个太慢了,上面代码已经在判断到RXNE时马上disable SPI了,但是明显,这个还是不够快。怎么办? 如何能提高速度? 这个折腾了很久,终于发现一个不合理的操作可以直接把速度成 DIV4,即12MB。这个就是先禁用SPI,  再判断RXNE…发现竟然是可以很稳定的工作。


	SPI1->CR1 |= SPI_CR1_SPE; //ENABLE SPI
g_bisTx=0;
     SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI

     while (!(SPI1->SR &  SPI_I2S_FLAG_RXNE)  ) ; // wait data received
	 dat = *(uint8_t*)&SPI1->DR;

这个确实也是神奇,特别写这个文章记录一下。

归纳的原理应该是SPI ENABLE就开始读了,这ENABLE和DISALBE的时间刚好就是12MB的速度,多一条指令都不行了。

同时也尝试了使用DMA去加速,但是同样因为这个时钟刹车停止问题,使用DMA时实际测试需要更慢才能稳定传输,也要去到SPI_BaudRatePrescaler_128。究其原因应该也是DMA停止时操作太多,无法简化,导致时钟无法及时停止,引起数据错误。

以下是三线SPI的DMA传输的代码,供参考。

void Rfchip_Spi_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
    DMA_InitTypeDef DMA_InitStructure={0};

	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);


	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;


	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 |  GPIO_Pin_15;
	

	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_0); 
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_0);

	SPI_I2S_DeInit(SPI1);
	spiInit.SPI_Direction = SPI_Direction_1Line_Tx;
	spiInit.SPI_Mode = SPI_Mode_Master;
	spiInit.SPI_DataSize  = SPI_DataSize_8b;
	spiInit.SPI_CPOL  = SPI_CPOL_Low;
	spiInit.SPI_CPHA = SPI_CPHA_1Edge;
	spiInit.SPI_NSS = SPI_NSS_Soft;
	spiInit.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_128;
	spiInit.SPI_FirstBit  = SPI_FirstBit_MSB;
	spiInit.SPI_CRCPolynomial = 7;
	SPI_Init(SPI1, &spiInit);
	
	SPI_SSOutputCmd(SPI1,DISABLE);
	  
	//NSS
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	SPI_TIModeCmd(SPI1,DISABLE);
	SPI_NSSPulseModeCmd(SPI1,DISABLE);


    DMA_DeInit(DMA1_Channel2);
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
	DMA_InitStructure.DMA_MemoryBaseAddr = 0;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	DMA_InitStructure.DMA_BufferSize = 0;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	DMA_Init(DMA1_Channel2, &DMA_InitStructure);


    DMA_DeInit(DMA1_Channel3);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = 0;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = 0;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel3, &DMA_InitStructure);




	SPI_Cmd(SPI1,ENABLE);



	//GIO
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	RFChip_Disable;
}



void Rf_Spi_Write_Bytes(uint8_t *pbuf, uint32_t len)
{

	if(!g_bisTx)
	{
		
		  SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
    	  SPI1->CR1 |= SPI_Direction_Tx;
    	g_bisTx=1;
	}

    DMA_Cmd(DMA1_Channel3,DISABLE);

	DMA1_Channel3->CMAR = (uint32_t)pbuf;
    DMA1_Channel3->CNDTR = len;
    DMA_Cmd(DMA1_Channel3,ENABLE);
    SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Tx,ENABLE);
    SPI1->CR1 |= SPI_CR1_SPE; //ENABLE SPI
    while(DMA_GetFlagStatus(DMA1_FLAG_TC3)==RESET);
    DMA_ClearFlag(DMA1_FLAG_TC3);
    SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Tx,DISABLE);

    while (SPI1->SR &  SPI_I2S_FLAG_BSY){}
	
	SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);
	DMA_Cmd(DMA1_Channel3,DISABLE);
	
 


}

void Rf_Spi_Read_Bytes(uint8_t *pbuf, uint32_t len)
{

	uint16_t Dis_SPI_CR1_SPE=(uint16_t)~((uint16_t)SPI_CR1_SPE);
	uint16_t dMA_DISABLE =  (uint16_t)(~DMA_CCR_EN);
	if(g_bisTx)
    {
	    SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
	    SPI1->CR1 &= SPI_Direction_Rx;
	     /* Clear FRXTH bit */
		SPI1->CR2 &= (uint16_t)~((uint16_t)SPI_CR2_FRXTH);
		/* Set new FRXTH bit value */
		SPI1->CR2 |= SPI_RxFIFOThreshold_QF;  //SET IT 8 BIT per READ, THAT is QUETER OF 32BIT
	    
	}
	g_bisTx=0;
    DMA1_Channel2->CCR &= dMA_DISABLE;  //DISABLE DMA CHANNEL2
	DMA1_Channel2->CMAR = (uint32_t)pbuf;
    DMA1_Channel2->CNDTR = len;
    SPI1->CR2 |= SPI_I2S_DMAReq_Rx; //DMA REQUEST
    SPI1->CR1 |= SPI_CR1_SPE; //ENABLE SPI
    DMA1_Channel2->CCR |= DMA_CCR_EN; //ENABLE DMA CHANNEL2

    while(!(DMA1->ISR & DMA1_FLAG_TC2));
     DMA1_Channel2->CCR &=dMA_DISABLE;  //DISABLE DMA CHANNEL2

    SPI1->CR1 &= Dis_SPI_CR1_SPE;//DISABLE SPI

    SPI1->CR2 &= (uint16_t)~SPI_I2S_DMAReq_Rx;

    DMA_ClearFlag(DMA1_FLAG_TC2);
    

}

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 硬件 SPI 半双工三线的研究心得

发表评论