求指教,STM32与FPGA使用SPI通信,跨时钟域问题如何解决呢
RT1、STM32作为Master,主频是72MHz,SPI1可以2分频、4分频、8分频、……
2、EP2C5作为Slaver,外部晶振为50MHz
SPI用的是0模式:CPOL=0 CPHA=0 即SCK第一个时钟(上升沿)边沿数据采样,第2个时钟(下降沿)边沿数据被锁存。
一个是36MHz、18MHz、9MHz……
一个是50MHz
时钟如何同步?!用FIFO吗?!
以下的方法经测试貌似不可取
module SPI_slave(clk, SCK, MOSI, MISO, SSEL, LED);
input clk;
input SCK, SSEL, MOSI;
output MISO;
output LED;
// sync SCK to the FPGA clock using a 3-bits shift register
reg SCKr;always @(posedge clk) SCKr <= {SCKr, SCK};
wire SCK_risingedge = (SCKr==2'b01);// now we can detect SCK rising edges
wire SCK_fallingedge = (SCKr==2'b10);// and falling edges
// same thing for SSEL
reg SSELr;always @(posedge clk) SSELr <= {SSELr, SSEL};
wire SSEL_active = ~SSELr;// SSEL is active low
wire SSEL_startmessage = (SSELr==2'b10);// message starts at falling edge
wire SSEL_endmessage = (SSELr==2'b01);// message stops at rising edge
// and for MOSI
reg MOSIr;always @(posedge clk) MOSIr <= {MOSIr, MOSI};
wire MOSI_data = MOSIr;
// we handle SPI in 8-bits format, so we need a 3 bits counter to count the bits as they come in
reg bitcnt;
reg byte_received;// high when a byte has been received
reg byte_data_received;
always @(posedge clk)
begin
if(~SSEL_active)
bitcnt <= 3'b000;
else
if(SCK_risingedge)
begin
bitcnt <= bitcnt + 3'b001;
// implement a shift-left register (since we receive the data MSB first)
byte_data_received <= {byte_data_received, MOSI_data};
end
end
always @(posedge clk) byte_received <= SSEL_active && SCK_risingedge && (bitcnt==3'b111);
// we use the LSB of the data received to control an LED
reg LED;
always @(posedge clk) if(byte_received) LED <= byte_data_received;
reg byte_data_sent;
reg cnt;
always @(posedge clk) if(SSEL_startmessage) cnt<=cnt+8'h1;// count the messages
always @(posedge clk)
if(SSEL_active)
begin
if(SSEL_startmessage)
byte_data_sent <= cnt;// first byte sent in a message is the message count
else
if(SCK_fallingedge)
begin
if(bitcnt==3'b000)
byte_data_sent <= 8'h00;// after that, we send 0s
else
byte_data_sent <= {byte_data_sent, 1'b0};
end
end
assign MISO = byte_data_sent;// send MSB first
// we assume that there is only one slave on the SPI bus
// so we don't bother with a tri-state buffer for MISO
// otherwise we would need to tri-state MISO when SSEL is inactive
endmodule
资料来源:http://www.fpga4fun.com/SPI2.html SPI协议的模式只和开始状态有关,不管是什么模式,SI总是在SCK上升沿被锁存,SO总是在SCK下降沿输出(JTAG例外)。要注意时序宽松度。该程序跑10M是没问题的,但是跑30M肯定出问题(采样频率应当至少为信号频率的2倍)。注意bitcnt计数器的值:1,2,3,4,5,6,7,0。即本来bitcnt=0时应当输出LSB,结果你输出0了。建议使用两个计数器分别做。 学习一下,最近在做相关的东西。
但是建议是否考虑用DPRAM来经行处理 根本不涉及跨时钟,你用SPI的CLK同步你的FPGA数据.你其他部分随意用你自己FPGA时钟 跟跨时域没关系吧,spi协议由spi的时钟来控制 wye11083 发表于 2012-4-10 23:22 static/image/common/back.gif
SPI协议的模式只和开始状态有关,不管是什么模式,SI总是在SCK上升沿被锁存,SO总是在SCK下降沿输出(JTAG ...
谢谢,观察入微哦,在数据存取时,我多打了一拍,现在是STM32 --> FPGA数据正确,有时也会出错,STM32 <-- FPGA 收不到数据的。
以下是STM32的代码:SPI是8分频的
#define SCS_HIGH GPIO_SetBits(GPIOA, GPIO_Pin_4)
#define SCS_LOW GPIO_ResetBits(GPIOA, GPIO_Pin_4)
void SPI1_Configeration(void)
{
GPIO_InitTypeDefGPIO_InitStructure;
SPI_InitTypeDefSPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //NSS(PA.4) 推免输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7; //MOSI(PA.7) SCK(PA.5) 复用推免输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //MISO(PA.6) 浮点输入
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);
////////////////////////////////////////////////////////////////////////////////////////
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE); //打开端口复用时钟、SPI1时钟
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主机模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//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型号
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //比特率8分频
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7;//定义了用于CRC值计算的多项式
SPI_Init(SPI1,&SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE); //使能SPI1
}
/**********************************************************
函数名称: SPI_SendByte
功 能: SPI1发/收一个字节数据(硬件SPI)
**********************************************************/
unsigned char SPI1_SendByte(unsigned char dt)
{
SCS_LOW;
//等待SPI1发送完毕
while((SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET));
SPI_I2S_SendData(SPI1,dt);
//等待SPI1接收完毕
while((SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET));
SCS_HIGH;
return SPI_I2S_ReceiveData(SPI1);
}
FPGA的SPI模块代码: 和上面差不多,由于个人习惯,改了一些名称
//`timescale 1ns/1ps
module SPI_Slave(clk,rst_n,
SCK,MOSI,MISO,NSS,INT,
Data_Ready,Data_Received
);
input clk; //FPGA clock,50MHz
input rst_n; //FPGA reset signal,fallingedge is effective
input SCK; //Master's SPI SCK signal,Idle:0/Risingedge:Sample/Fallingedge:Latched
input MOSI; //Master Out Slaver In
output MISO; //Master In Slaver Out
input NSS; //SPI Chip-select signal
output INT; //Slaver interrupt signal of requesting to send data to Master
output Data_Ready; //detect Data_Received ready or not
output Data_Received; //SPI_Bus receive data from the Master
//===============================================================================
//sync SCK to the FPGA clock using a 3-bits shift register
reg SCK_r;
always @(posedge clk or negedge rst_n)
if(!rst_n) SCK_r <= 3'b000;
else SCK_r <= {SCK_r,SCK};
wire SCK_risingedge = (SCK_r==2'b01); //Now we can detect SCK rising edges
wire SCK_fallingedge = (SCK_r==2'b10); //and falling edges
//the same thing for NSS
reg NSS_r;
always @(posedge clk or negedge rst_n)
if(!rst_n) NSS_r <= 3'b000;
else NSS_r <= {NSS_r,NSS};
wire NSS_Active = ~NSS_r; //NSS is active now
wire NSS_startmessage = (NSS_r==2'b10); //message starts at falling edge
//not be used//wire NSS_endmessage = (NSS_r==2'b01); //message stops at rising edge
//and for MOSI
reg MOSI_r;
always @(posedge clk or negedge rst_n)
if(!rst_n) MOSI_r <= 2'b00;
else MOSI_r <= {MOSI_r,MOSI};
wire MOSI_Data = MOSI_r;
//===============================================================================
//we handle SPI in 8-bits format,so we need a 3-bits counter to count the bits as they come in.
reg bitcnt;
reg byte_received; //high while a byte has already been received
reg byte_data_received;
always @(posedge clk or negedge rst_n)
if(!rst_n) begin bitcnt <= 3'b000; byte_data_received <= 7'h00; end
else if(!NSS_Active) bitcnt <= 3'b000;
else if(SCK_risingedge) begin
bitcnt <= bitcnt +1'b1;
//implement a shift-left register(since we receive the data MSB first)
byte_data_received <= {byte_data_received,MOSI_Data};
end
assign Data_Received = byte_data_received;
always @(posedge clk or negedge rst_n)
if(!rst_n) byte_received <= 1'b1;
else
byte_received <= NSS_Active && SCK_risingedge && (bitcnt==3'b111);
assign Data_Ready = byte_received;
//===============================================================================
//Here we use INT signal for test,we use the LSB of the data received to control an LED
reg INT;
always @(posedge clk) if(Data_Ready) INT <= byte_data_received;
// count the messages
reg cnt;
always @(posedge clk or negedge rst_n)
if(!rst_n) cnt <= 8'h00;
else if(NSS_startmessage) cnt <= cnt + 8'h1;//NSS_Active && SCK_risingedge && (bitcnt==3'b111)
//===============================================================================
reg byte_data_send;
always @(posedge clk)
if(NSS_Active)
begin
if(NSS_startmessage) byte_data_send <=cnt; //first byte send in a message is the message count
else
if(SCK_fallingedge)
begin
if(bitcnt==3'b000) byte_data_send <= 8'h00; //after that,we send 0s
else byte_data_send <= {byte_data_send,1'b0};
end
end
//always @(posedge clk or negedge rst_n)
// if(!rst_n) byte_data_send <= 8'd123;
// else if(NSS_Active&&SCK_fallingedge)
// byte_data_send <= {byte_data_send,1'b0};
//
assign MISO = byte_data_send; //send MSB first
endmodule
NJ8888 发表于 2012-4-11 09:12 static/image/common/back.gif
根本不涉及跨时钟,你用SPI的CLK同步你的FPGA数据.你其他部分随意用你自己FPGA时钟 ...
我也是这么想的 ,但FPGA在SPI数据存取上,由于时钟不同步,会有问题的,我再试试看…… qingyin2009 发表于 2012-4-11 09:45 static/image/common/back.gif
跟跨时域没关系吧,spi协议由spi的时钟来控制
还不太清楚,自己现在有点糊涂了,网上有人发论文,用的是SAM+FIFO做的 你要有逻辑分析仪或两通道数字示波器,一个看CLK 一个看DO 或DI(看你调试收发哪个)就能发现问题了 这是我的FPGA SPI程序,时序仿真正常,可以跑到130MHz(Spartan-IIE老芯片)。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 13:38:21 04/11/2012
// Design Name:
// Module Name: SPIGeneralTransceiver
// Project Name:
// Target Devices:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module SPIGeneralTransceiver(
input int_in_GCLK,
input int_in_GRST,
input port_in_SPICS,
input port_in_SPISI,
input port_in_SPISCK,
output port_out_SPISO,
output int_out_parallel_SPIDO,
output int_out_SPIDO_parallel_EN,
input int_in_parallel_SPIDI,
input int_in_SPIDI_parallel_EN
);
// According to SPI specification, data is latched on rising edge of SCK,
// and output latch on falling edge of SCK.
// Any time a valid command is received, valid data out is moved to cache.
reg r_port_in_SPISCK_delay;
wire w_port_in_SPISCK_Edge = ^r_port_in_SPISCK_delay;
reg r_port_in_SPICS_delay;
reg r_port_in_SPISI;
wire w_SPI_Receive_Reset = int_in_GRST & ~r_port_in_SPICS_delay;
always @(posedge int_in_GCLK or negedge int_in_GRST)
begin
if(~int_in_GRST)
begin
r_port_in_SPISCK_delay <= 0;
end
else
begin
r_port_in_SPICS_delay <= port_in_SPICS;
r_port_in_SPISCK_delay <= {r_port_in_SPISCK_delay,port_in_SPISCK};
r_port_in_SPISI <= port_in_SPISI;
end
end
// SPI Receive part.
reg rc_SPI_Receive; // Actually this counter represents SCLK count.
reg rs_SPI_Receive;
reg r_SPI_Receive_Byte;
reg r_SPI_Receive_parallel_SPIDO_EN;
assign int_out_parallel_SPIDO = r_SPI_Receive_Byte;
assign int_out_SPIDO_parallel_EN = r_SPI_Receive_parallel_SPIDO_EN;
// Use w_port_in_SPISCK_Edge and r_port_in_SPISCK_delay and r_port_in_SPISI
// SPI Transmit part
reg r_int_in_parallel_SPIDI_Cache;
reg r_int_in_parallel_SPIDI;
assign port_out_SPISO = r_int_in_parallel_SPIDI;
always @(posedge int_in_GCLK or negedge w_SPI_Receive_Reset)
begin
if(~w_SPI_Receive_Reset)
begin
r_int_in_parallel_SPIDI_Cache <= 0;
r_int_in_parallel_SPIDI <= 0;
end
else
begin
if(int_in_SPIDI_parallel_EN)
begin
r_int_in_parallel_SPIDI_Cache <= int_in_parallel_SPIDI;
end
else
begin
r_int_in_parallel_SPIDI_Cache <= r_int_in_parallel_SPIDI_Cache;
end
// Validate int_in_SPIDI_parallel_EN and w_port_in_SPISCK_Edge and ~r_port_in_SPISCK_delay
if(rc_SPI_Receive == 0)
begin
// Load input data
if(int_in_SPIDI_parallel_EN & ~r_port_in_SPISCK_delay)
begin
r_int_in_parallel_SPIDI <= int_in_parallel_SPIDI;
end
else
begin
if(w_port_in_SPISCK_Edge & ~r_port_in_SPISCK_delay)
r_int_in_parallel_SPIDI <= r_int_in_parallel_SPIDI_Cache;
else
r_int_in_parallel_SPIDI <= r_int_in_parallel_SPIDI;
end
end
else
begin
// Shift only on falling edge
if(w_port_in_SPISCK_Edge & ~r_port_in_SPISCK_delay)
r_int_in_parallel_SPIDI <= {r_int_in_parallel_SPIDI,1'b0};
else
r_int_in_parallel_SPIDI <= r_int_in_parallel_SPIDI;
end
end
end
always @(posedge int_in_GCLK or negedge w_SPI_Receive_Reset)
begin
if(~w_SPI_Receive_Reset)
begin
rc_SPI_Receive <= 0;
rs_SPI_Receive <= 0;
r_SPI_Receive_Byte <= 0;
r_SPI_Receive_parallel_SPIDO_EN <= 0;
end
else
begin
case (rs_SPI_Receive)
0:
begin
r_SPI_Receive_parallel_SPIDO_EN <= 0;
// Cache the first rising edge of SCK
if(w_port_in_SPISCK_Edge & r_port_in_SPISCK_delay)
begin
r_SPI_Receive_Byte <= {r_SPI_Receive_Byte,r_port_in_SPISI};
rc_SPI_Receive <= rc_SPI_Receive + 1;
rs_SPI_Receive <= 1;
end
else
begin
rs_SPI_Receive <= 0;
end
end
1:
begin
// Cache the 2-8 rising edge of SCK
if(w_port_in_SPISCK_Edge & r_port_in_SPISCK_delay)
begin
r_SPI_Receive_Byte <= {r_SPI_Receive_Byte,r_port_in_SPISI};
rc_SPI_Receive <= rc_SPI_Receive + 1;
if(rc_SPI_Receive == 7)
begin
// A byte is received
r_SPI_Receive_parallel_SPIDO_EN <= 1;
rs_SPI_Receive <= 0;
end
else
begin
// Not ready
rs_SPI_Receive <= 1;
end
end
else
begin
rs_SPI_Receive <= 1;
end
end
endcase
end
end
endmodule
输入和输出的EN都是高电平有效,其中int_接口没有延时,直接操作,port_in_有延时,port_out_看设计。
这个是TestBench
`timescale 1ns / 1ps
////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 13:59:22 04/11/2012
// Design Name: SPIGeneralTransceiver
// Module Name: D:/USBDev/USBDevelop/SPIGeneralTransceiver_tb.v
// Project Name:USBDevelop
// Target Device:
// Tool versions:
// Description:
//
// Verilog Test Fixture created by ISE for module: SPIGeneralTransceiver
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
////////////////////////////////////////////////////////////////////////////////
module SPIGeneralTransceiver_tb;
// Inputs
reg int_in_GCLK;
reg int_in_GRST;
reg port_in_SPICS;
reg port_in_SPISI;
reg port_in_SPISCK;
reg int_in_parallel_SPIDI;
reg int_in_SPIDI_parallel_EN;
// Outputs
wire port_out_SPISO;
wire int_out_parallel_SPIDO;
wire int_out_SPIDO_parallel_EN;
// Instantiate the Unit Under Test (UUT)
SPIGeneralTransceiver uut (
.int_in_GCLK(int_in_GCLK),
.int_in_GRST(int_in_GRST),
.port_in_SPICS(port_in_SPICS),
.port_in_SPISI(port_in_SPISI),
.port_in_SPISCK(port_in_SPISCK),
.port_out_SPISO(port_out_SPISO),
.int_out_parallel_SPIDO(int_out_parallel_SPIDO),
.int_out_SPIDO_parallel_EN(int_out_SPIDO_parallel_EN),
.int_in_parallel_SPIDI(int_out_parallel_SPIDO),
.int_in_SPIDI_parallel_EN(int_out_SPIDO_parallel_EN)
);
initial begin
// Initialize Inputs
int_in_GCLK = 0;
int_in_GRST = 0;
port_in_SPICS = 1;
port_in_SPISI = 0;
port_in_SPISCK = 0;
int_in_parallel_SPIDI = 0;
int_in_SPIDI_parallel_EN = 0;
// Wait 100 ns for global reset to finish
#100;
// Add stimulus here
int_in_GRST = 1;
#200;
port_in_SPICS = 0;
#9000;
port_in_SPICS = 1;
end
always begin
#325;
forever begin
port_in_SPISI = $random;
#25;
port_in_SPISCK = 1;
#50;
port_in_SPISCK = 0;
#25;
end
end
always #5 int_in_GCLK=~int_in_GCLK;
endmodule
习惯于在TestBench中使用串行口自收发,第一,写TestBench方便,第二,有没有错误只要看最后的数据对不对就知道了。 大家讨论的好热烈啊。
我之前也做过SPI Slave(用的是Spartan-3AN),而且是Quad SPI 四根data线的。Host端做处理可以跑到133MHz正确接收数据。 用FPGA 时钟采样SPI CLK的时钟进行处理就可以吧,采样时钟是SPI 时钟的2倍+ 一直在用fpga4fun上面的代码 没问题 我也准备在stm32和fpga使用spi进行数据通信,现在做数据的采集硬件电路,关注大家的讨论。把帖子收藏起来。 wye11083 发表于 2012-4-11 15:06 static/image/common/back.gif
这是我的FPGA SPI程序,时序仿真正常,可以跑到130MHz(Spartan-IIE老芯片)。
`timescale 1ns / 1ps
非常感谢wye11083兄的支持
上面fpga4fun的代码,只有STM32接收FPGA的数据MISO出错,可能是STM32有原因,回头我在测试你的代码看看。 我也准备在stm32和fpga使用spi进行数据通信, mark 本帖最后由 wye11083 于 2012-4-11 23:34 编辑
对了,想到一点,虽然我没用过STM,但我认为你要用STM收SPI数据时,应当先打开SPI接收允许,要不然SPI控制器本身是不知道什么时候该收,什么时候该发,所以就不收了。你看看datasheet,应该能找到对应寄存器的bit。
不好意思,忘了点回复了,请删除。 xzf962 发表于 2012-4-11 22:08 static/image/common/back.gif
非常感谢wye11083兄的支持
上面fpga4fun的代码,只有STM32接收FPGA的数据MISO出错,可能是STM32有原因, ...
对了,想到一点,虽然我没用过STM,但我认为你要用STM收SPI数据时,应当先打开SPI接收允许,要不然SPI控制器本身是不知道什么时候该收,什么时候该发,所以就不收了。你看看datasheet,应该能找到对应寄存器的bit。
wye11083 发表于 2012-4-11 23:33 static/image/common/back.gif
对了,想到一点,虽然我没用过STM,但我认为你要用STM收SPI数据时,应当先打开SPI接收允许,要不然SPI控 ...
wye11083兄,经板级验证,你的代码可行。
各位网友,代码使用前,请注意SPI的模式。
注意到一细节:wye11083兄的文档建立时间……再次表示感谢 mark~~~~~~~~~ FPGA收藏 收藏先~~~~~~~~~~~ 证实可以用了么??这个涉及到STM和FPGA,理解起来比较吃力啊~ 谢谢楼上的分享享! 这帖子太及时了:
为了进行STM32与FPGA之间的通信我先在STM32与STM32两个板之间通信(主SPI1,从SPI2)配置时全双工通信 但是数据总是传不正确(有时正确),但是我在一个板子上进行STM32(SPI1与SPI2)通信却可以。 我纠结都将近10天了。。。。 大神们,你们遇见过这问题吗?有成功的例程吗?明天我将程序整理一下打上来,
希望大家能帮帮忙!! 看看哪错了
^小K^ 发表于 2013-2-26 21:08 static/image/common/back.gif
这帖子太及时了:
为了进行STM32与FPGA之间的通信我先在STM32与STM32两个板之间通信(主SPI1,从SPI2)...
上面代码是正确的 看来FPGA嵌入ARM等硬核是未来潮流啊。。。 楼主你好我最近也在做stm32与fpga的spi通信程序调试总有问题麻烦加下qq请教下371125717 谢谢 MARk,最近用到stm32和Fpga双核心。。。 xzf962 发表于 2012-4-16 18:40
wye11083兄,经板级验证,你的代码可行。
各位网友,代码使用前,请注意SPI的模式。
问题解决了吗?可以参考 下你的源码不?
页:
[1]