当前位置:   article > 正文

FPGA——spi代码篇_spi指令

spi指令

一、FPGA 开发SPI基础

        为了避免每次SPI驱动重写,直接参数化,尽量一劳永逸。SPI master有啥用呢,你发现各种外围芯片的配置一般都是通过SPI配置的,只不过有三线和四线。SPI slave有什么用呢,当外部主机(cpu)要读取FPGA内部寄存器值,那就很有用了,FPGA寄存器就相当于RAM,cpu通过SPI寻址读写数据。代码仅供参考,勿做商业用途。

二、SPI三线,四线区别

        三线制指的是CS,CLK,MOMI,是半双工方式;四线制指的是 CS,CLK,MOSI和MISO,是全双工方式。

三、SPI代码构思      

      1. SPI salve

                1.支持三线SPI或者四线SPI。通过define切换。                                 

                2.支持指令长度、帧长自定义。                               

                3.工作时钟可自定义,大于SPI clk的2倍。

        用户只需修改:(1)几线SPI。(2)单帧长度。(3)指令长度。(4)寄存器开辟。

        注意:指令最高bit表示读写,低写高读,其余bit表示地址。指令接着为数据端,两者位宽之和即为SPI单帧长。

  1. //`define SPI_LINE //是否是三线SPI
  2. `define SPI_FRAME_WIDTH 16 //SPI一帧长度为16
  3. `define SPI_INS_WIDTH 8 //SPI指令长
  4. `timescale 1ns/1ps
  5. module spi_slave
  6. (
  7. input i_clk , //work clk
  8. input i_rst_n ,
  9. input i_spi_clk , //SPI clk
  10. input i_spi_cs , //SPI cs
  11. `ifdef SPI_LINE //条件编译
  12. inout io_spi_sdio
  13. `else
  14. input i_spi_mosi , //SPI mosi
  15. output o_spi_miso //SPI miso
  16. `endif
  17. );
  18. //位宽计算函数
  19. function integer clogb2 (input integer depth);
  20. begin
  21. for (clogb2=0; depth>0; clogb2=clogb2+1)
  22. depth = depth >>1;
  23. end
  24. endfunction
  25. reg r_cs = 1'b1; //打一拍
  26. always @(posedge i_clk)
  27. begin
  28. r_cs <= i_spi_cs;
  29. end
  30. reg [1:0] r_spi_clk_edge = 2'b00; //SPI clk边沿检测
  31. always @(posedge i_clk)
  32. begin
  33. r_spi_clk_edge <= {r_spi_clk_edge[0],i_spi_clk};
  34. end //always
  35. reg [clogb2(`SPI_FRAME_WIDTH-1)-1:0] r_spi_cnt = 'd0;
  36. always @(posedge i_clk)
  37. begin
  38. if (r_cs) //cs为高则归零
  39. r_spi_cnt <= 'd0;
  40. else if (r_spi_clk_edge == 2'b10) //下降沿才计数
  41. r_spi_cnt <= r_spi_cnt + 'd1;
  42. end
  43. 指令锁存
  44. reg [`SPI_INS_WIDTH-1:0] r_ins = 'd0;
  45. always @(posedge i_clk)
  46. begin
  47. if ((~r_cs) && (r_spi_clk_edge == 2'b01)) //上升沿锁存数据
  48. begin
  49. if ((r_spi_cnt >= 0) && (r_spi_cnt <= `SPI_INS_WIDTH-1))
  50. `ifdef SPI_LINE //条件编译
  51. r_ins <= {r_ins[`SPI_INS_WIDTH-2:0],io_spi_sdio};
  52. `else
  53. r_ins <= {r_ins[`SPI_INS_WIDTH-2:0],i_spi_mosi};
  54. `endif
  55. end
  56. end
  57. 数值写入
  58. reg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_data_rx = 'd0;
  59. always @(posedge i_clk)
  60. begin
  61. if ((~r_cs) && (r_spi_clk_edge == 2'b01)) //上升沿锁存数据
  62. begin
  63. if (r_spi_cnt >= `SPI_INS_WIDTH)
  64. `ifdef SPI_LINE
  65. r_data_rx <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],io_spi_sdio};
  66. `else
  67. r_data_rx <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],i_spi_mosi};
  68. `endif
  69. end
  70. end
  71. 用户寄存器定义
  72. reg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_reg0 = 'd0;
  73. reg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_reg1 = 'd0;
  74. reg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_reg2 = 'd0;
  75. reg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_reg3 = 'd0;
  76. always @(posedge i_clk,negedge i_rst_n)
  77. begin
  78. if (~i_rst_n)
  79. begin
  80. r_reg0 <= 'd0;
  81. r_reg1 <= 'd0;
  82. r_reg2 <= 'd0;
  83. r_reg3 <= 'd0;
  84. end
  85. else if ((~r_ins[`SPI_INS_WIDTH-1]) && (r_spi_cnt == (`SPI_FRAME_WIDTH-1)) && (~r_cs) && (r_spi_clk_edge == 2'b01))
  86. begin
  87. `ifdef SPI_LINE
  88. case (r_ins[`SPI_INS_WIDTH-2:0])
  89. 'd0:begin r_reg0 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],io_spi_sdio}; end
  90. 'd1:begin r_reg1 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],io_spi_sdio}; end
  91. 'd2:begin r_reg2 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],io_spi_sdio}; end
  92. 'd3:begin r_reg3 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],io_spi_sdio}; end
  93. endcase
  94. `else
  95. case (r_ins[`SPI_INS_WIDTH-2:0])
  96. 'd0:begin r_reg0 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],i_spi_mosi}; end
  97. 'd1:begin r_reg1 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],i_spi_mosi}; end
  98. 'd2:begin r_reg2 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],i_spi_mosi}; end
  99. 'd3:begin r_reg3 <= {r_data_rx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],i_spi_mosi}; end
  100. endcase
  101. `endif
  102. end
  103. end
  104. 寄存器值读出
  105. reg [`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1:0] r_data_tx = 'd0;
  106. always @(posedge i_clk)
  107. begin
  108. if (r_ins[`SPI_INS_WIDTH-1] && (~r_cs) && (r_spi_clk_edge == 2'b10))
  109. begin
  110. if (r_spi_cnt == (`SPI_INS_WIDTH-1))
  111. begin
  112. case (r_ins[`SPI_INS_WIDTH-2:0])
  113. 'd0:begin r_data_tx <= r_reg0; end
  114. 'd1:begin r_data_tx <= r_reg1; end
  115. 'd2:begin r_data_tx <= r_reg2; end
  116. 'd3:begin r_data_tx <= r_reg3; end
  117. endcase
  118. end
  119. else
  120. r_data_tx <= {r_data_tx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-2:0],1'b0};
  121. end
  122. end
  123. 读取输出
  124. `ifdef SPI_LINE
  125. assign io_spi_sdio = (r_ins[`SPI_INS_WIDTH-1]) ? (((r_spi_cnt>=`SPI_INS_WIDTH) && (r_spi_cnt<`SPI_FRAME_WIDTH)) ? r_data_tx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1] : 1'bz) : 1'bz;
  126. `else
  127. assign o_spi_miso = ((r_spi_cnt>=`SPI_INS_WIDTH) && (r_spi_cnt<`SPI_FRAME_WIDTH)) ? r_data_tx[`SPI_FRAME_WIDTH-`SPI_INS_WIDTH-1] : 1'b0;
  128. `endif
  129. endmodule // end the spi_slave model

     2.SPI master

        spi master内部仅仅封装SPI驱动,写入值读出控制由上层控制,这部分逻辑很simple,不赘述。用户只需给入SPI帧及控制使能即可。

        用户只需修改parameter参数:(1)单帧长;(2)指令长;(3)数据长;(4)工作时钟;(5)SPI clk。

        实现不使用状态机,采用线性序列计数法。

  1. //`define SPI_LINE //是否是三线SPI
  2. `timescale 1ns/1ps
  3. module spi_master
  4. #(parameter p_spi_frame_width = 16, //SPI单帧长度
  5. parameter p_spi_ins_width = 8 , //指令长度
  6. parameter p_spi_data_width = 8 //读出数据长度
  7. )
  8. (
  9. input i_clk , //系统时钟
  10. input i_rst_n ,
  11. input i_flag , //检测到flag的上升沿则启动一次传输,一个时钟周期即可
  12. input [p_spi_frame_width-1:0] i_spi_data ,
  13. output o_spi_cs ,
  14. output o_spi_clk ,
  15. `ifdef SPI_LINE //条件编译
  16. inout io_spi_sdio ,
  17. `else
  18. input i_spi_miso , //SPI miso
  19. output o_spi_mosi , //SPI mosi
  20. `endif
  21. output o_transfer_done , //单次传输完成
  22. output [p_spi_data_width-1:0] o_spi_data //读取数据
  23. );
  24. parameter p_clk_fre = 200; //XXM时钟频率
  25. parameter p_spi_clk_fre = 0.5*1000; //SPI 时钟速率,表示1M
  26. parameter p_clk_div = p_clk_fre * 1000/p_spi_clk_fre/2-1;
  27. parameter p_spi_cnt_max = p_spi_frame_width*2-1;
  28. parameter p_spi_ins_max = p_spi_ins_width*2-1;
  29. //位宽计算函数
  30. function integer clogb2 (input integer depth);
  31. begin
  32. for (clogb2=0; depth>0; clogb2=clogb2+1)
  33. depth = depth >>1;
  34. end
  35. endfunction
  36. //把最大值赋值给线型,直接用p_clk_div仿真有问题,但实际上板是可以的
  37. wire [clogb2(p_clk_div)-1:0] w_clk_div;
  38. assign w_clk_div = p_clk_div;
  39. 时钟分频
  40. reg [clogb2(p_clk_div)-1:0] r_cnt_div = 'd0;
  41. always @(posedge i_clk)
  42. begin
  43. if (r_cnt_div == w_clk_div)
  44. r_cnt_div <= 'd0;
  45. else
  46. r_cnt_div <= r_cnt_div + 'd1;
  47. end //always
  48. wire w_clk_en; //分频时钟使能
  49. assign w_clk_en = (r_cnt_div == w_clk_div) ? 1'b1 : 1'b0;
  50. reg [1:0] r_flag_edge = 2'b00;
  51. reg [clogb2(p_spi_cnt_max)-1:0] r_spi_cnt = 'd0;
  52. always @(posedge i_clk) //flag边沿检测
  53. begin
  54. r_flag_edge <= {r_flag_edge[0],i_flag};
  55. end
  56. //flag信号展宽到低速时钟域
  57. reg r_flag_enlarge = 1'b0;
  58. always @(posedge i_clk)
  59. begin
  60. if (r_flag_edge == 2'b01) //上升沿拉高
  61. r_flag_enlarge <= 1'b1;
  62. else if (r_spi_cnt == p_spi_ins_max) //足够长的高电平才拉低
  63. r_flag_enlarge <= 1'b0;
  64. end
  65. reg [1:0] r_flag_enlarge_edge = 2'b00;
  66. always @(posedge i_clk)
  67. begin
  68. if (w_clk_en)
  69. r_flag_enlarge_edge <= {r_flag_enlarge_edge[0],r_flag_enlarge};
  70. end
  71. reg r_cs = 1'b1;
  72. always @(posedge i_clk)
  73. begin
  74. if (w_clk_en)
  75. begin
  76. if (r_flag_enlarge_edge == 2'b01) //检测到需要进行SPI操作
  77. r_cs <= 1'b0;
  78. else if (r_spi_cnt == p_spi_cnt_max) //计数到最大值表示一次SPI完成
  79. r_cs <= 1'b1;
  80. end
  81. end
  82. always @(posedge i_clk)
  83. begin
  84. if (w_clk_en)
  85. begin
  86. if(~r_cs) //在操作区间计数
  87. r_spi_cnt <= r_spi_cnt + 'd1;
  88. else
  89. r_spi_cnt <= 'd0;
  90. end
  91. end
  92. 数据传输段
  93. reg [p_spi_frame_width-1:0] r_data = 'd0;
  94. always @(posedge i_clk)
  95. begin
  96. if (w_clk_en)
  97. begin
  98. if (r_flag_enlarge_edge == 2'b01) //上升沿刷入
  99. r_data <= i_spi_data;
  100. else if (r_spi_cnt[0] == 1'b1) //数据移动
  101. r_data <= {r_data[p_spi_frame_width-2:0],1'b1};
  102. end
  103. end
  104. 数据读取段
  105. reg [p_spi_data_width-1:0] r_data_read = 'd0;
  106. always @(posedge i_clk)
  107. begin
  108. if (w_clk_en)
  109. begin
  110. if (i_spi_data[p_spi_frame_width-1] && (r_spi_cnt > p_spi_ins_max) && (r_spi_cnt[0] == 1'b0)) //是读
  111. `ifdef SPI_LINE
  112. r_data_read <= {r_data_read[p_spi_data_width-2:0],io_spi_sdio};
  113. `else
  114. r_data_read <= {r_data_read[p_spi_data_width-2:0],i_spi_miso};
  115. `endif
  116. end
  117. end
  118. SPI输出段
  119. assign o_spi_cs = r_cs;
  120. assign o_spi_clk = r_cs ? 1'b0 : r_spi_cnt[0];
  121. SPI SDIO的输入输出切换
  122. `ifdef SPI_LINE
  123. assign io_spi_sdio = (i_spi_data[p_spi_frame_width-1]) ? (((r_spi_cnt >= 'd0) && (r_spi_cnt <= p_spi_ins_max)) ? r_data[p_spi_frame_width-1] : 1'bz ) : r_data[p_spi_frame_width-1];
  124. `else
  125. assign o_spi_mosi = r_data[p_spi_frame_width-1];
  126. `endif
  127. assign o_transfer_done = ((~r_cs) && (r_spi_cnt == p_spi_cnt_max)) ? 1'b1:1'b0;
  128. assign o_spi_data = r_data_read;
  129. endmodule // end the spi_master model

        3.前仿真代码

 

  1. `define DATA 8'ha5
  2. //`define SPI_LINE
  3. timeunit 1ns;
  4. timeprecision 1ps;
  5. module top;
  6. parameter p_sim_end_time = 1000000; //ns
  7. logic l_clk = 1'b0;
  8. always #2.5 l_clk = ~l_clk;
  9. 复位
  10. logic l_rst_n = 1'b0;
  11. initial begin
  12. #100 l_rst_n = 1'b1;
  13. end
  14. wire io_sdio;
  15. wire o_spi_cs;
  16. wire o_spi_clk;
  17. wire o_transfer_done;
  18. wire [7:0] o_spi_data;
  19. 多个数据操作模式
  20. reg r_flag = 1'b0;
  21. reg [1:0] r_first_cnt = 2'b00;
  22. always @(posedge l_clk,negedge l_rst_n)
  23. begin
  24. if (~l_rst_n)
  25. r_first_cnt <= 2'b00;
  26. else if (r_first_cnt == 2'd3)
  27. r_first_cnt <= r_first_cnt;
  28. else
  29. r_first_cnt <= r_first_cnt + 2'd1;
  30. end
  31. reg [1:0] r_transfer_done_edge = 2'b00;
  32. always @(posedge l_clk)
  33. begin
  34. r_transfer_done_edge <= {r_transfer_done_edge[0],o_transfer_done};
  35. end
  36. reg [3:0] r_transfer_cnt = 4'd0;
  37. always @(posedge l_clk)
  38. begin
  39. if ((r_first_cnt == 2'd2) && (r_transfer_cnt < `TRANSFER_NUMBER))
  40. r_flag <= 1'b1;
  41. else if ((r_transfer_done_edge == 2'b10) && (r_transfer_cnt < `TRANSFER_NUMBER-1))
  42. r_flag <= 1'b1;
  43. else
  44. r_flag <= 1'b0;
  45. end
  46. always @(posedge l_clk)
  47. begin
  48. if (r_transfer_done_edge == 2'b10)
  49. r_transfer_cnt <= r_transfer_cnt + 'd1;
  50. end
  51. reg [15:0] r_in_data = 16'd0;
  52. always @(*)
  53. begin
  54. if (~l_rst_n) //仿真不执行此段仿真会有问题
  55. r_in_data = 16'h0000;
  56. else
  57. begin
  58. case(r_transfer_cnt)
  59. 4'd0:begin r_in_data = {8'h00,8'h43}; end
  60. 4'd1:begin r_in_data = 16'h0132; end
  61. 4'd2:begin r_in_data = 16'h0245; end
  62. 4'd3:begin r_in_data = 16'h0367; end
  63. 4'd4:begin r_in_data = 16'h8000; end
  64. 4'd5:begin r_in_data = 16'h8100; end
  65. 4'd6:begin r_in_data = 16'h8200; end
  66. 4'd7:begin r_in_data = 16'h8300; end
  67. default:begin r_in_data = 16'h0000; end
  68. endcase
  69. end
  70. end
  71. wire w_spi_miso;
  72. wire w_spi_mosi;
  73. spi_master inst_spi_master (
  74. .i_clk (l_clk),
  75. .i_rst_n (),
  76. .i_flag (r_flag),
  77. .i_spi_data (r_in_data),
  78. .o_spi_cs (o_spi_cs),
  79. .o_spi_clk (o_spi_clk),
  80. `ifdef SPI_LINE
  81. .io_spi_sdio (io_sdio),
  82. `else
  83. .i_spi_miso (w_spi_miso),
  84. .o_spi_mosi (w_spi_mosi),
  85. `endif
  86. .o_transfer_done (o_transfer_done),
  87. .o_spi_data (o_spi_data)
  88. );
  89. spi_slave inst_spi_slave (
  90. .i_clk (l_clk),
  91. .i_rst_n (l_rst_n),
  92. .i_spi_clk (o_spi_clk),
  93. .i_spi_cs (o_spi_cs),
  94. `ifdef SPI_LINE
  95. .io_spi_sdio (io_sdio)
  96. `else
  97. .i_spi_mosi (w_spi_mosi),
  98. .o_spi_miso (w_spi_miso)
  99. `endif
  100. );
  101. initial begin
  102. #p_sim_end_time $stop;
  103. end
  104. endmodule

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/829493
推荐阅读
相关标签
  

闽ICP备14008679号