关于STM32与FPGA的通信(SPI通信方式)(3-实现MCU与FPGA的互相通信)

        前几篇的原理与代码写得不够详细并且有疏漏得地方,在本篇中会进行纠正

        首先,我们先认识到我们需要在FPGA中创建什么类型的模块,一个ROM供MCU读取,一个RAM供MCU写入,若干计数器。为了实现这些模块的通信,在这中间我们需要使用一些模块来达到目的。最为重要的模块就是充当MOSI与MISO的处理转化输入输出的模块,我们暂且简称其为SPI_BUS模块,这个模块的一个作用是处理MCU输入的32位数据,将其转化为地址和数据两部分数据发送给各个模块

由上图实例化的模块图可以看出,输出一个16位的数据和一个32位的使能标志,有关使能标志位如何寻址相应寄存器在上一章节讲过,不再赘述。而这边的I_DIN通过一个MUX选择器来选择MCU读取的数据

读取哪一位数据完全由MCU来指示,只有当MUX选择器的使能位W_O_WE[7]接收到来自MCU的指令置1才会启用,即寻址成功。而W_O_WD[15..0]则是选择这16个挂载的附件的哪一个作为输出给MCU。

        这是SPI_BUS的顶层代码,链接各个端口

module spi_bus_32b_A15RD1WR0(
  I_CLK     ,
  I_CS      ,
  I_SCLK    ,
  I_SDIN    ,
  I_DIN     ,
  I_DEN     ,
  O_SDOUT   ,
  O_WD      ,
  O_WE      ,
  O_TESTOUT1);

input I_CLK    ;
input I_CS     ;
input I_SCLK   ;
input I_SDIN   ;
output          O_SDOUT; 
output [16-1:0] O_WD   ;
output [32-1:0] O_WE   ;
output            O_TESTOUT1;

input[16-1:0]  I_DIN     ;
input          I_DEN     ;


wire[32-1:0] W_U_spi_O_DOUT;
wire         W_U_spi_O_DOV ;
spi_in_to_parallel_data_out_parallel_in_to_spi_out  U_spi_s2p(
  .I_CLK     (I_CLK ),
  .I_CS      (I_CS  ),
  .I_SCLK    (I_SCLK),
  .I_SDIN    (I_SDIN),
  .I_DIN     (I_DIN ),
  .I_DEN     (I_DEN ),
  .O_DOUT    (W_U_spi_O_DOUT),
  .O_DOV     (W_U_spi_O_DOV ),
  .O_SDOUT   (O_SDOUT       ),
  .O_TESTOUT1());

wire [16-1:0]   W_U_dec_O_DAT;
wire [32 -1:0]  W_U_dec_O_DOV;

  
addr_decode U_decode(
  .I_CLK      (I_CLK                ),
  .I_ENA      (W_U_spi_O_DOV        ), // input enable
  .I_DIN      (W_U_spi_O_DOUT[15: 0]),
  .I_AIN      (W_U_spi_O_DOUT[31:16]),
  .O_DAT      (W_U_dec_O_DAT        ),
  .O_DOV      (W_U_dec_O_DOV        ),
  .O_TESTOUT1 ());

assign O_WD = W_U_dec_O_DAT;
assign O_WE = W_U_dec_O_DOV;

  
endmodule

然后这边的串入并出,并入串出的模块比起上一节多了输出的功能

需要注意:R_sdin_msb实际上是用来存储I_SDIN的最高位的,另一个R_sclk_up_cnt就是用来计数sck上升次数

R_sdout_shift[16:0]用于将并行输入转换为串行数据的移位寄存器

这段代码的核心两个时序逻辑一个是处理MCU向FPGA串行写入数据一个是MCU读取FPGA的数据这时是并入串出的

module spi_in_to_parallel_data_out_parallel_in_to_spi_out(
  I_CLK     ,
  I_CS      ,
  I_SCLK    ,
  I_SDIN    ,
  I_DIN     , 
  I_DEN     ,
  O_DOUT    ,
  O_DOV     ,
  O_SDOUT   ,
  O_TESTOUT1);


output O_TESTOUT1;

input I_CLK    ;
input I_CS     ;
input I_SCLK   ;
input I_SDIN    ;

input          [16-1:0]  I_DIN;
input                    I_DEN;

reg            [16-1:0]  R1_I_DIN;

reg  R1_I_CS  ,R2_I_CS ,R3_I_CS  ;
reg  R1_I_SCLK ,R2_I_SCLK,R3_I_SCLK ;
reg  R1_I_SDIN ,R2_I_SDIN, R3_I_SDIN ;//级联寄存器缓冲减少亚稳态出现概率
reg  W_I_CS_up , W_I_CS_down, W_I_SCLK_up;

reg [32-1:0] R_sdin_shift, R_out_buf;
output          O_SDOUT;
output [32-1:0] O_DOUT ;
output          O_DOV  ;   
reg             O_DOV  ;   
reg    [8-1:0]  R_sclk_up_cnt;
reg             R_sdin_msb;//用于存储SDIN的第一位数据,用来决定读写,后续的输出标志位与之有关,为1表示为读,W0R1
reg [16:0]   R_sdout_shift; // 16+1 bit, one bit(as stuff ) more than data word length

// pipeline
//                          | meta stability
//            |  R3_I_CS     |R2_I_CS    |R1_I_CS    |  I_CS       <-- Input
//            |  R3_I_SCLK   |R2_I_SCLK  |R1_I_SCLK  |  I_SCLK
//            |  R3_I_SDIN   |R2_I_SDIN  |R1_I_SDIN  |  I_SDIN
//            |              |W_I_CS_up  
//            |              |W_I_CS_down
//            |              |W_I_SCLK_up
//            |R_sclk_up_cnt |
//R_sdin_msb  |R_sdout_shift
//

always @ (posedge I_CLK) begin//主要是处理读写位R_sdin_msb和时钟上升沿次数(采样了几次数据)
  // cs neg edge clear the sclk counter and sdin msb reg to zero
  if(W_I_CS_down) begin
    R_sclk_up_cnt <= 0;
    R_sdin_msb    <= 0;  
  end
  else begin
    // store the first bit of SDIN
    if((R_sclk_up_cnt == 0)&& W_I_SCLK_up)
      R_sdin_msb    <= R3_I_SDIN; 
    // sclk counter when CS is enable(low)
    if(~(R1_I_CS) && W_I_SCLK_up)
      R_sclk_up_cnt <= R_sclk_up_cnt + 1;
  end
end

always @ (*) begin//捕捉上升沿与下降沿
  W_I_CS_up   = ((R3_I_CS == 1'b0)&&( R2_I_CS == 1'b1)) ? 1'b1 : 1'b0;
  W_I_CS_down = ((R3_I_CS == 1'b1)&&( R2_I_CS == 1'b0)) ? 1'b1 : 1'b0;
  W_I_SCLK_up  = ((R3_I_SCLK == 1'b0)&&( R2_I_SCLK == 1'b1)) ? 1'b1 : 1'b0;
end
always @ (posedge I_CLK) begin
  R1_I_CS   <= I_CS       ;
  R1_I_SCLK <= I_SCLK     ;
  R1_I_SDIN <= I_SDIN     ;
  R2_I_CS   <= R1_I_CS    ;
  R2_I_SCLK <= R1_I_SCLK  ;
  R2_I_SDIN <= R1_I_SDIN  ;
  R3_I_SDIN <= R2_I_SDIN  ;
  R3_I_CS   <= R2_I_CS    ;    
  R3_I_SCLK <= R2_I_SCLK  ;
end
wire W_spi_is_wr; assign W_spi_is_wr =(~R_sdin_msb); 
wire W_spi_wr_out_en = W_I_CS_up  & (~R_sdin_msb) ;//表明是写操作
// shift input SDIN to parallel OUT
// load parallel output buf
always @ (posedge I_CLK) begin//处理MCU向FPGA写入数据。并且是串入并出
  if(W_I_CS_down)
    R_sdin_shift <= 0;
  else
    if(W_I_SCLK_up)
      R_sdin_shift <= {R_sdin_shift[30:0],R2_I_SDIN};
 //  if(W_I_CS_up& (~R_sdin_msb))
 //    R_out_buf <= R_sdin_shift;
 //  O_DOV <= W_I_CS_up & (~R_sdin_msb); // msb is 1 for read don't output data
   if(W_spi_wr_out_en)begin
     R_out_buf <= R_sdin_shift;
     O_DOV <= W_spi_wr_out_en; // msb is 1 for read don't output data
	end
end  
assign O_DOUT = R_out_buf;

// load parallel input buff
// shift  parallel input to  SDOUT
always @ (posedge I_CLK) begin//处理MCU读取FPGA数据,并且是并入串出
  if(I_DEN)//数据使能有效
    R1_I_DIN <= I_DIN;//I_DIN是数据
  if(W_I_CS_down)//捕捉到起始信号
    R_sdout_shift <= {1'b0,R1_I_DIN};//注意,R_sdout_shift是17位的
  else
    if(W_I_SCLK_up && R_sdin_msb) // msb == 1, read mode ,shift out
    R_sdout_shift <= {R_sdout_shift[15:0],1'b0};
end
assign O_SDOUT = R_sdout_shift[16];//串出读数据

assign O_TESTOUT1 =  W_I_SCLK_up ;

endmodule

        再就是写入的RAM模块的代码,

这个双口RAM实现异步读写,其中有个新的语法

实际上就是定义以恶个数组,表示这个数组有

2^ADDRWL个元素。每个元素有ADDRWL的宽度。图中这样写是为了直观的看出多少位多少个元素

module dpram16_spi_wr_test(
  I_CLK ,
  I_WA  ,
  I_WAE ,
  I_WD  ,
  I_WDE ,
  O_RD  );

input           I_CLK ; 
input  [16-1:0] I_WA  ;  
input           I_WAE ;
input  [16-1:0] I_WD  ;
input           I_WDE ;
output [16-1:0] O_RD  ;


reg   [16-1:0] R_wa     ;  
reg   [16-1:0] R1_I_WD  ;
reg            R1_I_WDE ;

reg   [7-1:0]  R_ra ;

always @ (posedge I_CLK) begin
  if(I_WAE)begin
		R_wa <= I_WA;
		R1_I_WD   <=   I_WD ;  
		R1_I_WDE  <=   I_WDE;//缓冲
		// read address just inc 1 every clk period , for read test
		R_ra <=  R_ra + 1;
	end
end

dpram_spi_wr U_ram_128(
  .WCLK    (I_CLK   ),   // write clock
  .RCLK    (I_CLK   ),   // read clock   
  .WA      (R_wa    ),   // write address
  .WD      (R1_I_WD ),   // write data
  .WE      (R1_I_WDE),   // write enable
  .RA      (R_ra    ),   // read address
  .RD      (O_RD    ));  // read data
// external set param
defparam U_ram_128.DATAWL    = 16;
defparam U_ram_128.ADDRWL    = 7;





endmodule


module dpram_spi_wr(
  WE      ,   // write enable
  WCLK    ,   // write clock
  RCLK    ,   // read clock   
  WA      ,   // write address
  RA      ,   // read address
  WD      ,   // write data
  RD      );  // read data
// external set param
parameter DATAWL    = 0;
parameter ADDRWL    = 0;
parameter C2Q       = 0;
  input                    WE, WCLK, RCLK;
  input   [ADDRWL -1:0]    WA, RA; 
  input   [DATAWL -1:0]    WD;
  output  [DATAWL -1:0]    RD;

  reg[DATAWL-1:0] RD;
  reg[DATAWL-1:0] mem [(1 << ADDRWL)-1:0];

  always @ (posedge WCLK) begin
    if(WE)
      mem[WA] <= #C2Q WD;
  end
  always @ (posedge RCLK) begin
    RD  <= #C2Q mem[RA];
  end
endmodule   // module dptbram() 

有关FPGA的部分就大概写做完了,最后的BDF如图所示

        然后就是有关主机操控从机的部分。

        向计数器发送代码的部分泰国简单就不多赘述了

    spi_wr_32bit_MSB_first(0x00, 0x00, 0x02, 0x02); // 16 bit counter 1
    spi_wr_32bit_MSB_first(0x00, 0x01, 0x01, 0x01); // 16 bit counter 2
    spi_wr_32bit_MSB_first(0x00, 0x00, 0x04, 0x04); // 16 bit counter 1
    spi_wr_32bit_MSB_first(0x00, 0x01, 0x05, 0x05); // 16 bit counter 2

    spi_wr_32bit_MSB_first(0x00, 0x02, 0x01, 0x02); // 32 bit counter  L
    spi_wr_32bit_MSB_first(0x00, 0x03, 0x03, 0x04); // 32 bit counter  H
    spi_wr_32bit_MSB_first(0x00, 0x04, 0x00, 0x00); // 32 bit counter  Update
    
    spi_wr_32bit_MSB_first(0x00, 0x02, 0x04, 0x03); // 32 bit counter  L
    spi_wr_32bit_MSB_first(0x00, 0x03, 0x02, 0x01); // 32 bit counter  H
    spi_wr_32bit_MSB_first(0x00, 0x04, 0x00, 0x00); // 32 bit counter  Update

然后是向RAM写入的功能,这部分的代码比较简单,就是2个循环从头写到尾和从尾写到头的测试

其中两次传输数据对RAM的写入对应了FPGA中的W_O_WE[5]和W_O_WE[6],这是一个不断对RAM进行写入的过程

这两行用来不断变化地址以及数据同时保证不超出RAM的范围

这段代码将第七位标志位置1,打开mux,从中读取数据

void spi_wr_addr_7b_data_16b_ram_128(){
  int i = 0, wa = 0, wd = 0, wa_H, wa_L, wd_H, wd_L;
  // test case 1
  for (i = 0 ; i < 128; i ++){
    wa = i&(128-1); wa_H = (wa >> 8) & 0xff; wa_L = wa  & 0xff;
    wd = wa*255   ; wd_H = (wd >> 8) & 0xff; wd_L = wd  & 0xff;
    // ADDRESS MUST BE WRITE FIRST
    //                     A_H   A_L , D_H , D_L
    spi_wr_32bit_MSB_first(0x00, 0x05, wa_H, wa_L); // write wa reg
    spi_wr_32bit_MSB_first(0x00, 0x06, wd_H, wd_L); // write wd reg
  } 
  // Delay(1000*1000*10);
  // test case 2
  for (i = 0 ; i < 128; i ++){
    wa = i&(128-1)      ; wa_H = (wa >> 8) & 0xff; wa_L = wa  & 0xff;
    wd = (128-1-wa)*255 ; wd_H = (wd >> 8) & 0xff; wd_L = wd  & 0xff;
    // ADDRESS MUST BE WRITE FIRST
    //                     A_H   A_L , D_H , D_L
    spi_wr_32bit_MSB_first(0x00, 0x05, wa_H, wa_L); // write wa reg
    spi_wr_32bit_MSB_first(0x00, 0x06, wd_H, wd_L); // write wd reg
  } 
}

接下来有关MCU如何从FPGA中读取数据,首先我们要知道MCU读取的FPGA输出的数据是串出的,所以我们首先需要一个读取串出数据的函数,这个函数中我们使用了一个向FPGA写入翻个数据的函数

void spi_wr_sda_out(int val){
  GPIO_WriteBit(PORT_MY_SPI_O_SDA,PIN_MY_SPI_O_SDA      ,(val&0x1)?Bit_SET:Bit_RESET    );
}

第一位写1,表示读W0R1,此时的FPGA的SPI模块接收到第一位,R_sdin_msb已经置1,输出时序已准备.

读取数据的循环先写入1是为了保证采样的稳定性

unsigned int spi_wr_1bit_cmd_read_16bit(){
  int i = 0;
  unsigned int rd_val = 0; unsigned char sda_in = 0;
  unsigned char buf = 0xaa;
  unsigned char rd16[16] = {0};
  spi_wr_cs(1);
  spi_wr_sck(1);
  spi_wr_sda_out(1);
  // drop down cs
  spi_wr_cs(0);
  Delay(1);
  // drop down sck
  spi_wr_sck(0);
  Delay(1);
  // write first data bit as 1
  spi_wr_sda_out(1);
  Delay(1);
  spi_wr_sck(1);
  Delay(1);
  spi_wr_sck(0);

  // read data
  for(i = 0; i < 16; i ++){
    spi_wr_sda_out(0);
    buf = buf << 1;
    Delay(1);
    spi_wr_sck(1);
    Delay(1);
    spi_wr_sck(0);
    // read sda in ,at sclk neg edge
    sda_in = spi_rd_sda_in();
    // printf("sda_in = %x \r\n",sda_in);
    rd_val = (rd_val << 1)| (sda_in&0x01);
    rd16[i] = sda_in;
  }
  spi_wr_cs(1);
  spi_wr_sck(1);
//  printf("rd_val = %x \r\n",rd_val);
#if 0 // bit print debug
  for(i = 0; i < 16; i ++){
    printf("rd16[%d] = %x \r\n",i, rd16[i]);
  }
#endif
  return rd_val;
}

然后就是有关通过MUX读取其负载原件数据的代码,比较简单,就是读取0号位置上挂载的计数器的值,注意开启I_DEN这个使能位,当时我Debug了半天。

unsigned int spi_read_rom()
{
	unsigned int rd_val = 0;
	spi_wr_32bit_MSB_first(0x00, 0x07, 0x00, 0x00);
	rd_val = spi_wr_1bit_cmd_read_16bit();
	return rd_val;
}

然后就是进一步读取挂载在1号位的ROM的数据

unsigned short rom_rd[128] = {0};
unsigned int spi_rd_addr_7b_data_8_rom_128(){
    unsigned int rom_addr = 0, rd_val = 0;
    
    //printf("#------------------------------------------------------------------------------- \r\n");
    //printf("# ROM READ TEST \r\n", rom_addr, rd_val);
    // first write the read select value
    spi_wr_32bit_MSB_first(0x00, 0x07, 0x00, 0x01); //  select the input port 1 
    for(rom_addr = 0; rom_addr < 128; rom_addr ++){
      //  write the rom read address
      spi_wr_32bit_MSB_first(0x00, 0x08, 0x00, rom_addr&0xFF); //  select the input port 1 
      //  read rom value
      rd_val = spi_wr_1bit_cmd_read_16bit();
      // save the low 16bit to array
      rom_rd[rom_addr] = (unsigned short) rd_val & 0xFFFF;
    }
    // print the rom value
    /*for(rom_addr = 0; rom_addr < 128; rom_addr ++){
      printf("# ROM[%4d] = 0x%-4X \r\n", rom_addr, rom_rd[rom_addr]);
    }*/
		return rd_val;
}

目前最后一部还未完全验证。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值