LinCoding

【原创】详细解析FPGA与STM32的SPI通信(一)

0
阅读(18666)

【主题】:详细解析FPGA与STM32的SPI通信(一)

【作者】:LinCoding

【时间】:2016.11.26

【声明】:转载、引用,请注明出处

      昨天把SPI彻底的又搞了一遍,感觉之前学STM32时学的SPI只是皮毛,这次学习FPGA时候,才真正算是把SPI吃透了。

      As we all know, SPI有四种模式,但是STM32与FPGA通信的话推荐使用SPI_CPOL_Low和SPI_CPHA_1Edge这个模式,也就是时钟信号线空闲为低,上升沿采样,因为这样更加适合FPGA进行处理。

      使用SPI要注意以下几点:

      1、时钟和片选是由主机提供,从机只负责接收。

      2、对STM32来说,片选可选用硬件或软件,具体有什么区别请看数据手册。

      3、SPI的通信速率不能太快,否则数据会出错。

      4、如果进行双工通信的话,主机必须在发送完数据后多发送一个8位数据,笔者通常发送0xFF,这样从机才能将最后一个有效数据发过来,具体原因请百度,不多解释。

       先说第一点和第二点:    

 主机给从机发数据进行单工通信的话很简单,因为时钟和片选是由主机提供的。

      但是,从机给主机发数据进行单工通信的时,由于时钟和片选是由主机提供的,主机并不知道从机什么时候给主机发,也就不知道什么时候给从机提供时钟信号和片选信号,当然了,也不会知道从机什么时候已经发完数据,什么时候停止提供时钟信号和片选信号。那这该怎么办?

 同样,如果是双工通信,且从机所发的数据比主机数量多,主机发完以后如果停止提供片选和时钟信号的话,从机剩余的数据就无法传送过来,那这又该怎么办?

       笔者想到了一个解决办法,就是增加一根从机与主机的连线,普通IO口即可,这根线平时为高,当从机准备向主机发数据时,把这根线拉低,主机检测到这根线拉低,则提供片选和时钟信号,当从机发完数据以后,把这根线拉高,主机检测到这根线拉高,则主机停止提供片选和时钟信号线。

       这样就完美的解决了这一问题。

       再说第三点,速率问题:

       对STM32来说,笔者使用的是SPI2,而SPI2硬件是挂在APB1总线上,也就是36Mhz低速外设总线,这样根据硬件的设定,速率可以在以下这四种中进行选择,

SPI_BaudRatePrescaler_2   2分频   -- 18Mhz
SPI_BaudRatePrescaler_8   8分频   -- 4.5Mhz
SPI_BaudRatePrescaler_16  16分频  -- 2.25Mhz
SPI_BaudRatePrescaler_256 256分频 -- 140.625Khz

 但是要注意,速率太快会出错!笔者FPGA使用100Mhz速率,按说可以采样到18Mhz的数据,但是,当STM32的SPI设置为18Mhz时会出错,4.5Mhz及以下均没有问题。当笔者将FPGA提高到200Mhz时,SPI的18Mhz仍然不行,因此笔者怀疑,频率太高的话对布局布线要求较高,而笔者采用的是杜邦线进行连接,应该是使用杜邦线不能满足那么高频率的要求。

       好了,基本上就是上面四个注意点,剩下的就是时序问题了,下面开始解析整个系统的程序:

       STM32的程序相对简单,那就先说STM32的程序吧。

void SPI2_Init(void)
{
 	GPIO_InitTypeDef GPIO_InitStructure;
	SPI_InitTypeDef  SPI_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,  ENABLE);	
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB

	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB

	GPIO_SetBits(GPIOB, GPIO_Pin_12);   //CS
	GPIO_ResetBits(GPIOB, GPIO_Pin_13); //SCK
	GPIO_SetBits(GPIOB, GPIO_Pin_14);   //MISO
	GPIO_ResetBits(GPIOB, GPIO_Pin_15); //MOSI
	
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;		
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_CRCPolynomial = 7;
	SPI_Init(SPI2, &SPI_InitStructure); 
	SPI2_SetSpeed(SPI_BaudRatePrescaler_8);
	SPI_Cmd(SPI2, ENABLE); 
}

笔者使用的是原子的例程,使用库函数进行配置是很简单的,注意的是,STM32设置为SPI_Mode_Master,SPI_DataSize_8b,SPI_CPOL_Low,SPI_CPHA_1Edge,SPI_NSS_Soft,还有就是SPI_FirstBit_MSB,这样就OK了!

int main(void)
{	 
	u8 i = 0;
	u8 dataTemp;
	delay_init();	    	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	uart_init(9600);	 	
	LED_Init();		  		
	SPI2_Init();	 

	while(1)
	{
		for ( i=0; i < 255; i++ )
		{
			dataTemp = SPI2_ReadWriteByte(i); 
			printf("%d\r\n",dataTemp);
			delay_ms(100);
		}
		dataTemp = SPI2_ReadWriteByte(0xFF);
		printf("%d\r\n",dataTemp);
		delay_ms(100);
	}
}

主函数中就是循环发送0~255,并且接收FPGA发来的数据,打印到串口进行显示。可以看到我在发送完数据以后,特地多发了一个0xFF。

 笔者用示波器将STM32发出的数据抓了一下,见下图:

blob.png

 为了图片好看,使用STM32只发送了一个0xAA,笔者采用了SPI_BaudRatePrescaler_256 256分频 -- 140.625Khz,可以看到示波器抓到的频率为140.5Khz,没差多少,数据是10101010也就是0xAA啦,下一步,就是使用FPGA模仿这个时序了。

由于内容太多,剩余的内容请看——详细解析FPGA与STM32的SPI通信(二)