精品博文详细解析FPGA与STM32的SPI通信(二)
一个双肩 背包
有多难?
戳一下试试看!
→_→
长摁识别
【主题】:详细解析FPGA与STM32的SPI通信(二)
【作者】:LinCoding
【声明】:转载、引用,请注明出处
本篇文章承接——详细解析FPGA与STM32的SPI通信(一),真是内容有点多,不得不分成两篇文章来讲。上文说道用FPGA来模仿STM32发出的SPI的协议。
1、SPI_Receiver模块的程序:
module spi_receiver ( inputclk,//global clock inputrst_n,//global reset inputspi_cs, inputspi_sck, inputspi_mosi, outputreg[7:0] rxd_data, outputregrxd_flag );
第一部分是输入输出定义,没什么可说的,对于接收数据的模块,要增加接收完成标志信号,以便其他模块读取数据 。
//----------------------------------- //synchronize the input signal regspi_cs_r0,spi_cs_r1; regspi_sck_r0,spi_sck_r1; regspi_mosi_r0,spi_mosi_r1; always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) begin spi_cs_r0<= 1'b1;spi_cs_r1<= 1'b1; spi_sck_r0<= 1'b0;spi_sck_r1<= 1'b0; spi_mosi_r0<= 1'b0;spi_mosi_r1<= 1'b0; end else begin spi_cs_r0<= spi_cs;spi_cs_r1<= spi_cs_r0; spi_sck_r0<= spi_sck;spi_sck_r1<= spi_sck_r0; spi_mosi_r0<= spi_mosi; spi_mosi_r1<= spi_mosi_r0; end end reg[3:0]rxd_cnt /*synthesis noprune*/; wiremcu_cs= spi_cs_r1; wiremcu_data= spi_mosi_r1; wiremcu_read_flag = ( spi_sck_r0 & ~spi_sck_r1) ? 1'b1 : 1'b0;//sck posedge capture wiremcu_read_done = ( spi_cs_r0 & ~spi_cs_r1 & (rxd_cnt == 4'd8) ) ? 1'b1 : 1'b0;
第二部分是一个重点:
首先,由于FPGA作为从机,接收STM32所发出的CS,SCK和MOSI信号,因此对于此类异步信号,需要利用主时钟做同步处理,最常用的方法就是打两拍,这在按键消抖的文章中有讲过。
其次,由于STM32的SPI模式选择为SPI_CPOL_Low和SPI_CPHA_1Edge这个模式,因此要在SCK时钟的上升沿进行采样,所以定义了mcu_read_flag这个信号,以捕获SCK的上升沿。
最后,还要知道8位的数据什么时候读取完毕了,根据上篇文章中示波器中的图,可以采用CS的上升沿作为数据读取完毕标志,因此定义了mcu_read_done信号,来监测CS的上升沿,但是由于STM32在复位阶段会有CS的抖动,因此最好加上rxd_cnt==8这个条件,以使得数据准确无误!
//----------------------------------- //sample input MOSI reg[7:0]rxd_data_r; always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) begin rxd_cnt<= 4'd0; rxd_data_r<= 8'd0; end else if ( ! mcu_cs ) if ( mcu_read_flag ) begin rxd_data_r[3'd7-rxd_cnt]<= mcu_data; rxd_cnt<= rxd_cnt + 1'b1; end else begin rxd_data_r<= rxd_data_r; rxd_cnt<= rxd_cnt; end else begin rxd_data_r<= rxd_data_r; rxd_cnt<= 4'd0; end end
第三部分就是进行数据的采样,看图说话,笔者在testbench中发了0xaa,0x55和0xff三个数,可以看到,都可以完美检测到。
这里有一个问题需要注意:
能否把上述代码的else if 部分改写成以下代码?
else if ( mcu_read_flag && ! mcu_cs ) begin rxd_data_r[3'd7-rxd_cnt]<= mcu_data; rxd_cnt<= rxd_cnt + 1'b1; end else begin rxd_data_r <= rxd_data_r; rxd_cnt <= rxd_cnt; end
这样看起来使得代码很简洁,但是却没有地方写rxd_cnt <= 4'd0;使得rxd_cnt无法恢复初值。因此笔者修改如下:
else if ( mcu_read_flag && ! mcu_cs ) if ( rxd_cnt < 4'd8 ) begin rxd_data_r[3'd7-rxd_cnt]<= mcu_data; rxd_cnt<= rxd_cnt + 1'b1; end else begin rxd_data_r<= rxd_data_r; rxd_cnt<= 4'd0; end else begin rxd_data_r<= rxd_data_r; rxd_cnt<= rxd_cnt; end
理想很美好,感觉可以了,看仿真吧:
结果只能识别第一个0xaa,因为缺少一个mcu_read_flag把rxd_cnt清零!因此没有办法,只能写成最开始那种形式!
//----------------------------------- //output always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) begin rxd_data<= 8'd0; rxd_flag<= 1'b0; end else if ( mcu_read_done ) begin rxd_data<= rxd_data_r; rxd_flag<= 1'b1; end else begin rxd_data<= rxd_data; rxd_flag<= 1'b0; end end
第四部分是同步输出rxd_data和rxd_flag,这在按键消抖的实验中已经用过了,见以下仿真图:
=====================================================
2、下面是SPI_Transfer模块的程序:
module spi_transfer ( inputclk,//global clock inputrst_n,//global reset inputspi_cs, inputspi_sck, outputregspi_miso, inputtxd_en, input[7:0] txd_data, outputregtxd_flag );
第一部分是输入输出定义,需要说明的是对于发送类的模块,无论是串口发送,SPI发送,都需要发送使能信号 ,如本例中的txd_en。
当然了,有发送使能,大家会想到什么?
是使用状态机的IDLE来等待使能信号的到来!笔者在——《详细解析74HC595驱动程序》这篇文章中说过!因此写Verilog程序只要掌握了相应的套路,模式,其实一点也不难!当然,就像接收模块的rxd_flag一样,少不了发送完成标志信号txd_flag,以供其他模块使用。
//----------------------------------- //synchronize the input signal regspi_cs_r0,spi_cs_r1; regspi_sck_r0,spi_sck_r1; always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) begin spi_cs_r0<= 1'b1;spi_cs_r1<= 1'b1; spi_sck_r0<= 1'b0;spi_sck_r1<= 1'b0; end else begin spi_cs_r0<= spi_cs;spi_cs_r1<= spi_cs_r0; spi_sck_r0<= spi_sck;spi_sck_r1<= spi_sck_r0; end end wiremcu_cs= spi_cs_r1; wiremcu_write_flag = ( ~spi_sck_r0 & spi_sck_r1) ? 1'b1 : 1'b0;//sck negedge capture wiremcu_write_done = ( spi_cs_r0 & ~spi_cs_r1 ) ? 1'b1 : 1'b0;//cs posedge capture wiremcu_write_start = ( ~spi_cs_r0 & spi_cs_r1 ) ? 1'b1 : 1'b0;//cs negedge capture
第二部分和spi_receiver的那部分类似,就不多做介绍了!
//----------------------------------- //FSM: encode localparamT_IDLE= 2'd0; localparamT_START= 2'd1; localparamT_SEND= 2'd2; localparamSPI_MISO_DEFAULT = 1'b1; //----------------------------------- //transfer FSM reg[1:0]txd_state; reg[3:0]txd_cnt /*synthesis noprune*/; always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) begin txd_cnt<= 4'd0; spi_miso<= SPI_MISO_DEFAULT; txd_state<= T_IDLE; end else case ( txd_state ) T_IDLE: begin txd_cnt<= 4'd0; spi_miso<= SPI_MISO_DEFAULT; if ( txd_en ) txd_state<= T_START; else txd_state<= T_IDLE; end T_START: begin if ( mcu_write_start ) begin spi_miso <= txd_data[3'd7-txd_cnt[2:0]]; txd_cnt <= txd_cnt + 1'b1; txd_state<= T_SEND; end else begin spi_miso <= spi_miso; txd_cnt <= txd_cnt; txd_state<= T_START; end end T_SEND: begin if ( mcu_write_done ) txd_state<= T_IDLE; else txd_state<= T_SEND; if ( ! mcu_cs ) if ( mcu_write_flag ) begin if ( txd_cnt < 4'd8 ) begin spi_miso <= txd_data[3'd7-txd_cnt[2:0]]; txd_cnt <= txd_cnt + 1'b1; end else begin spi_miso <= 1'b1; txd_cnt <= txd_cnt; end end else begin spi_miso<= spi_miso; txd_cnt<= txd_cnt; end else begin spi_miso<= SPI_MISO_DEFAULT; txd_cnt<= 4'd0; end end default: begin txd_cnt<= 4'd0; spi_miso<= SPI_MISO_DEFAULT; txd_state<= T_IDLE; end endcase end
第三部分就是长长的发送状态机了,首先在IDLE态等待使能信号的到来,使能信号到来之后,进入发送状态。
有一点需要注意,笔者的发送状态,第一位数据的发送时以CS信号的下降沿作为标志 ,之后的数据发送均以SCK的下降沿作为标志 ,这是为何?请看仿真图:
可以看到当FPGA给STM32发送数据时,STM32会在SCK的上升沿进行读取,如果FPGA仅仅在SCK的下降沿进行设置数据的话,SCK的第一个上升沿,由于FPGA还没有设置数据,导致STM32采到的高电平,也就是无论发什么数据,8位数据的最高位都是1,这是不合理的,因此,第一个数据必须在CS变为低电平的时候就设置好,之后在SCK的下降沿设置,这样可以完美发送8位数据!
//----------------------------------- //output always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) txd_flag<= 1'b0; else txd_flag<= mcu_write_done; end
最后一部分是产生txd_flag信号,虽然很简单,但是笔者还是要说两句,为何不写成以下形式呢?
//----------------------------------- //output always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) txd_flag<= 1'b0; else if ( mcu_write_done ) txd_flag<= 1'b1; else txd_flag <= 1'b0; end
写成上述代码,一点问题没有,但是不简洁,因此推荐第一种,事实上,在笔者的按键消抖中,就是第一种用法!
最后呢,一切都是那么完美,完美的时序,完美的结果!
两块STM32之间的SPI主从通信实例
之前分享过的SPI通讯实例:STM32硬件SPI主从通信实例,是基于一块STM32的两个SPI通讯。如果要进行两块STM32之间的SPI通讯,需要注意一些什么呢?
两块STM32之间的SPI通讯平时用的比较少,之前我也没有用过,网上也查了很多资料,没有找到现成的,能直接用的例子(做软件的不就是copy吗=v=)。
所以只能自己来实现了,本以为与在同一片STM32上做SPI主、从机通信一样,以为挺简单的,但是实际做起来还是遇到了不少问题,比如出现数据移位、多出了一些数据等问题。下面简单分享一下实现过程:
一、整体框图及说明
这里使用STM32F429IGT6作为主机,STM32F103ZET6作为从机,都配置为全双工。本例要实现的功能就是主、从机之间的数据互传。
主机往从机发送的数据为:
从机往主机发送的数据为:
二、关键代码
主机关键代码:
从机关键代码:
可见,主机与从机的代码大多都一样。只是从机多了一步启动传输的操作,这一步很关键,少了这一步传输就不正常。这是为了制造主机发送的同时也要接收到数据的条件。这一点参考手册里也有相关说明:
此处,要营造这样的条件,必须先启动从机,然后再启动主机。只有保证主机发送的同时有接收到数据,才能保证其时序的正常,否则可能会产生数据错位,或者会产生多余数据等情况。
三、调试
我们平时在做实际的开发时,一般很难做到把所有代码写完,跑一遍就能成功,都是需要进行各个子模块的调试,一步一步来,确保各个子模块都没有问题之后,整体跑起来自然就比较稳定。
一些经验丰富的软件工程师常会教导一些年轻的软件工程师:在接到一个开发任务之前,先不要急着码代码,首先需要明确你这项任务的需求是什么,把任务分解成各个模块,然后在电脑上或纸上画出整体框图,确保框图的正确性之后,再根据框图来编写代码、调试。
此处,我们要调试SPI主从通信,自然也是这样分模块进行调试的:
确认主机是否能正确发送数据确认从机是否能正确发送数据(返回数据给从机)确认从机是否接收到主机发过来的数据确认主机是否接收到从机发过来的数据若这几个点明确了,都没问题之后。就可以明确我们的SPI主、从机的基本通讯没有问题了,之后就可以进行我们的协议方面处理了(本例中没有这一部分)。下面分别看一些这几个点:
1、确认主机是否能正确发送数据
方法:使用逻辑分析仪捕捉主机的MOSI、SCK这两条信号线,查看其波形。实际测得地结果如下:
其中,白色为SCK信号线波形,橙色为MOSI信号线波形。显然,从MOSI波形可以看出其与我们主机发送的数据一致,自然就可以确认主机发送数据没问题了。
此处,细心的朋友可能会发现时钟线波形的两个数据交互处的高电平总是宽一些,这里我们的SPI传输数据的位数设置为8bit,则每一个数据对应的第8位对应的时钟信号的高电平总会长一些;若我们的SPI传输数据的位数配置为16位,则第16位对应的时钟信号的高电平总会长一些。
出现这个现象其实与我们的代码是有一定关系的。实际测试中发现是因为我们用while循环来做逻辑处理的问题,想办法把while等待替换为if判断,就可以改善这个问题。但是考虑到这并不会影响我们的数据,并且另一方面还有助于我们分析波形,所以使用while。
2、确认从机是否能正确发送数据(返回数据给从机)
使用逻辑分析仪来捕捉MISO的波形,其波形如下(此处只捕捉了数据波形):
显然,我们的从机发送数据也是没问题的。
3、确认从机是否接收到主机发过来的数据
这里使用在线调试的方法检测从机的接收buf,结果如下:
显然,从机收到的数据与主机发送的数据一致,说明从机接收也是没有问题的。由于手头里只有一个仿真器,所以也就没有同时监测主机的接收buf,监视主机的接收buf与监视从机的方法是一样的。
四、调试过程需要注意的问题
1、两块板子一定要共地。
2、两块板子的MOSI与MISO不需要交叉连接。
End:以上就是本次的分享,如有错误,欢迎指出!如果这篇文章对你有帮助,期望你的转发、点赞~谢谢
相关问答
STM32 和FPGA通过 SPI 进行通讯?既然是stm32和fpga,那为何不用同步通信或者spi?这样在fpga上的设计简单,通信速度也快。既然是stm32和fpga,那为何不用同步通信或者spi?这样在fpga上的设计简...
怎么利用 stm32 进行数据的 spi 传输?简单用DMASPI1接收数据自存储片内RAM做判断接受完再用DMa发送SPI2简单用DMASPI1接收数据自存储片内RAM做判断接受完再用DMa发送SPI2
STM32 的 SPI 从机接收数据错误是怎么回事?你用示波器抓一下,首先确定问题是发送错误,还是接收错误。抓的时候,要连同CLK一起抓。如果确定收错了,看看中断中是不是做了太多的工作,导致读取的时间晚了...
求助烧录 stm32 的 spi 扩展flash的方法?stm32没这个功能呢。nxp的倒是可以,比如LPC43x0系列的单片机内部没有rom,就用spiflash来当存储器,不过这时候运行的速度就降低了很多。stm32没这个功能呢。...
STM32 是什么,是32位的单片机吗?STM32是一款普通的单片机,一款能够让普通工科学校的孩子吃上饭的单片机。1、嵌入式软件工程师的一个方向,就是对STM32进行编程。一般谈起嵌入式,首先想到的...
利用ZIGBEE建立网络, stm32 与PC机怎样加入网络实现无线通信?与电脑通信可以用RS232(USART)这个最简单,PC(电脑)端一般都现成的串口驱动,直接调用即可。或者通过以太网,STM32一般分内置网口或外部模块实现。前者像ST...
stm32 与lcd什么协议连接?有spi,8080并口,LTDC接口等。有spi,8080并口,LTDC接口等。
STM32 中,DMA和IIC好像都是用来传输数据的,有什么区别吗?DMA和IIC是两种完全不同的东西。IIC是一种通讯方式、一种通讯协议,用于芯片之间或者用于板子之间等近距离的通讯。而DMA是一种控制器、单片机的片上资源,是直存...
STM32 属于什么?STM32,从字面上来理解,ST是意法半导体,M是Microelectronics的缩写,32表示32位,合起来理解,STM32就是指ST公司开发的32位微控制器。AR...
STM32 F103V系列的ARM处理芯片有5个串口?一般我们指的串口就特指的是USART,你上面的UARST应该是写错了。SPI,I2C可以统称为串行通信。STM32F103系列最多好像只有3个串口,具体对应的位置,在对应芯片技...