【原创】详细解析FPGA与STM32的SPI通信(一)
0赞【主题】:详细解析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发出的数据抓了一下,见下图:
为了图片好看,使用STM32只发送了一个0xAA,笔者采用了SPI_BaudRatePrescaler_256 256分频 -- 140.625Khz,可以看到示波器抓到的频率为140.5Khz,没差多少,数据是10101010也就是0xAA啦,下一步,就是使用FPGA模仿这个时序了。
由于内容太多,剩余的内容请看——详细解析FPGA与STM32的SPI通信(二)