• SPI总线协议


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

    目录

    SPI总线的定义

    SPI总线工作方式

    SPI驱动Verilog实现

    总结


    SPI总线的定义

    SPI(Serial Peripheral Interface)是一种同步串行通信协议,用于在微控制器、传感器、存储器等外设之间进行数据交换。SPI总线是一种全双工的通信方式,它由一个主设备(Master)和一个或多个从设备(Slave)组成。

    spi总线的特点:

    1、传输方式:SPI总线使用同步的时钟信号进行通信,主设备控制时钟信号的频率和极性。数据在时钟的边沿上升或下降时转移。SPI总线支持全双工通信,意味着主设备和从设备可以同时发送和接收数据。

    2、线路结构:SPI总线具有四条线路:时钟线(SCLK),主设备输出的数据线(MOSI),主设备接收的数据线(MISO)和片选线(CS)。

            时钟线(SCLK):时钟信号线,由主机发出,不同的主机可能具有不同的时钟速率,也就是通信频率。

            主设备输出的数据线(MOSI):主机输出数据,从机接收数据。(M代表Master,S代表Slave)

            主设备接收的数据线(MISO):主机接收数据,从机输出数据。

            片选线(CS):由主机选择与某一从机进行通讯。常为低电平有效

    一主一从模式:

    一主多从模式:

    (PS:借用一下大佬孤独的单刀的图,侵权请联系我,会立刻删除,原文章链接:FPGA实现的SPI协议(一)----SPI驱动_fpga spi_孤独的单刀的博客-CSDN博客)

    SPI总线工作方式

    SPI的工作模式通常由两个因素决定:一是时钟极性(CPOL,Clock Polarity),二是时钟相位(Clock Phase)。其中时钟极性决定了SCLK在空闲时是低电平还是高电平,时钟相位决定了在什么沿采集数据。

    (PS:借用一下大佬孤独的单刀的图,侵权请联系我,会立刻删除,原文章链接:)

            

    • 模式0:CPOL=0,CPHA=0。SCK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换
    • 模式1:CPOL=0,CPHA=1。SCK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
    • 模式2:CPOL=1,CPHA=O。SCK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
    • 模式3:CPOL=1,CPHA=1。SCK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换

    通过象限可以将这四种模式进行简要概括:

    SPI总线的优缺点:

    优点:

            1、灵活:SPI可以使用不同的工作模式,以满足不同场景的需要。SPI主设备可以配置不同的时钟频率,灵活可变。

            2、高速:相比与串口(UART)、IIC接口,SPI具有很高的通信频率

            3、简单的线路结构,只有SCLK、MOSI、MISO、CS 4条线路

            4、不限于8位,一次可以传输任意大小的字

    缺点:

            1、仅支持一个主设备,从设备被动响应,不支持从设备主动传输数据。

            2、虽然SPI总线线路相对简单,但每个外设都需要至少四条线路,因此在连接多个外设时,线路数量会增加。(每增加一个从机都会增加一个片选信号)

            3、主/从机没有应答信号

    --------------------------------------------------------------------------------------

    SPI驱动Verilog实现

    说明:模式0,一主一从模式,使用一段式状态机实现。

    (PS:不使用状态机的SPI驱动程序请阅读大佬孤独的单刀的文章:

    FPGA实现的SPI协议(一)----SPI驱动_fpga spi_孤独的单刀的博客-CSDN博客)

    主机代码:

    1. `timescale 1ns / 1ps
    2. //
    3. // Company:
    4. // Engineer:
    5. //
    6. // Create Date: 2023/09/01 17:04:04
    7. // Design Name:
    8. // Module Name: SPI_derive
    9. // Project Name:
    10. // Target Devices:
    11. // Tool Versions:
    12. // Description:
    13. //
    14. // Dependencies:
    15. //
    16. // Revision:
    17. // Revision 0.01 - File Created
    18. // Additional Comments:
    19. // SPI总线主机驱动程序
    20. //
    21. module SPI_derive #(
    22. parameter integer DW = 8
    23. )
    24. (
    25. input sys_clk , //系统时钟50M
    26. input sys_rst , //复位信号低电平有效
    27. input[DW-1:0] din , //要发送的数据
    28. input spi_start , //SPI驱动程序启动信号,一个高电平
    29. input spi_end , //SPI驱动程序结束信号
    30. output reg [DW-1:0] dout , //接收的数据
    31. output reg rec_over , //单个字节接收完毕信号
    32. output reg send_over , //单个字节发送结束信号
    33. output reg sclk , //SPI程序的SCLK 本程序是对系统时钟的2分频
    34. output reg cs_n , //SPI片选信号
    35. output reg mosi , //SPI输出,给从机发送数据
    36. input miso //SPI输入,从机发送过来的数据
    37. );
    38. localparam idle = 3'b001 ;
    39. localparam send_recv = 3'b010 ;
    40. localparam over = 3'b100 ;
    41. reg[2:0] state ;
    42. reg[DW-1:0] recv_data = 'd0 ;
    43. reg clk_2d = 'd0 ;
    44. reg[2:0] send_cnt = 'd0 ;
    45. reg[2:0] recv_cnt = 'd0 ;
    46. reg[1:0] cs_n_cnt = 'd0 ;
    47. always@(posedge sys_clk,negedge sys_rst)begin
    48. if(!sys_rst == 1)begin
    49. state <= idle;
    50. cs_n <= 1'b1;
    51. sclk <= 1'b0;
    52. mosi <= 1'b1;
    53. send_cnt<= 'd0 ;
    54. recv_cnt<= 'd0 ;
    55. dout <= 'd0 ;
    56. send_over <= 1'b0;
    57. rec_over <= 1'b0;
    58. cs_n_cnt <= 2'b0;
    59. end
    60. else begin
    61. case(state)
    62. idle:
    63. begin
    64. if(spi_start == 1)begin
    65. state <= send_recv;
    66. end
    67. else begin
    68. state <= state;
    69. end
    70. end
    71. send_recv: //send and receive
    72. begin
    73. if(spi_end == 1)begin
    74. state <= over;
    75. end
    76. else begin
    77. state <= state;
    78. end
    79. if(cs_n_cnt == 2 || sclk == 1)begin //发送 相对于CS的下降沿,发送延迟1个SCLK周期
    80. mosi <= din[DW-1-send_cnt];
    81. if(send_cnt < 3'b111)begin
    82. send_cnt <= send_cnt + 1 ;
    83. end
    84. else begin
    85. send_cnt <= 3'b0;
    86. end
    87. end
    88. else begin
    89. mosi <= mosi;
    90. send_cnt <= send_cnt;
    91. end
    92. if(send_cnt == 3'b111 && sclk == 1)begin
    93. send_over <= 1'b1;
    94. end
    95. else begin
    96. send_over <= 1'b0;
    97. end
    98. ///
    99. if(cs_n_cnt > 2 && sclk == 0)begin //接收
    100. recv_data[DW-1-recv_cnt] <= miso;
    101. if(recv_cnt < 3'b111)begin
    102. recv_cnt <= recv_cnt + 1;
    103. rec_over <= 1'b0;
    104. end
    105. else begin
    106. rec_over <= 1'b1;
    107. recv_cnt <= 3'b0;
    108. end
    109. end
    110. else begin
    111. recv_data<= recv_data;
    112. recv_cnt <= recv_cnt;
    113. rec_over <= rec_over;
    114. end
    115. if(recv_cnt == 3'b000 && sclk == 1)begin
    116. rec_over <= 1'b1;
    117. dout <= recv_data;
    118. end
    119. else begin
    120. rec_over <= 1'b0;
    121. dout <= 'd0;
    122. end
    123. cs_n <= 1'b0;
    124. if(cs_n_cnt < 3)begin
    125. cs_n_cnt <= cs_n_cnt + 1;
    126. end
    127. else begin
    128. cs_n_cnt <= cs_n_cnt;
    129. end
    130. if(cs_n_cnt < 3)begin //空余出1个的SCLK周期,使得主从机收发的数据对其
    131. sclk <= sclk;
    132. end
    133. else begin
    134. sclk <= !sclk;
    135. end
    136. end
    137. over:
    138. begin
    139. state <= idle;
    140. cs_n <= 1'b1;
    141. sclk <= 1'b0;
    142. mosi <= 1'b1;
    143. send_cnt <= 'd0 ;
    144. recv_cnt<= 'd0 ;
    145. dout <= 'd0 ;
    146. send_over <= 1'b0;
    147. rec_over <= 1'b0;
    148. cs_n_cnt <= 2'b0;
    149. end
    150. endcase
    151. end
    152. end
    153. endmodule

    从机代码:

    1. `timescale 1ns / 1ps
    2. //
    3. // Company:
    4. // Engineer:
    5. //
    6. // Create Date: 2023/09/01 17:04:04
    7. // Design Name:
    8. // Module Name: SPI_derive
    9. // Project Name:
    10. // Target Devices:
    11. // Tool Versions:
    12. // Description:
    13. //
    14. // Dependencies:
    15. //
    16. // Revision:
    17. // Revision 0.01 - File Created
    18. // Additional Comments:
    19. // SPI总线从机驱动程序
    20. //
    21. module spi_slave #(
    22. parameter integer DW = 8
    23. )
    24. (
    25. input sys_clk , //系统时钟50M
    26. input sys_rst , //复位信号低电平有效
    27. input[DW-1:0] din , //要发送的数据
    28. output reg spi_start , //SPI驱动程序启动信号,通知后级SPI程序已经启动
    29. output reg spi_end , //SPI驱动程序结束信号,通知后级SPI程序已经结束
    30. output reg [DW-1:0] dout , //接收的数据
    31. output reg rec_over , //单个字节接收完毕信号
    32. output reg send_over , //单个字节发送结束信号
    33. input sclk , //SPI程序的SCLK 本程序是对系统时钟的2分频
    34. input cs_n , //SPI片选信号
    35. input mosi , //SPI输入,主机发送过来的数据
    36. output reg miso //SPI输出,从机发送的数据
    37. );
    38. localparam idle = 3'b001 ;
    39. localparam send_recv = 3'b010 ;
    40. localparam over = 3'b100 ;
    41. reg[2:0] state ;
    42. reg[DW-1:0] recv_data = 'd0 ;
    43. reg clk_2d = 'd0 ;
    44. reg[2:0] send_cnt = 'd0 ;
    45. reg[2:0] recv_cnt = 'd0 ;
    46. reg sd_rc_flag = 'd0 ;
    47. always@(posedge sys_clk,negedge sys_rst)begin
    48. if(!sys_rst == 1)begin
    49. state <= idle;
    50. send_cnt<= 'd0 ;
    51. recv_cnt<= 'd0 ;
    52. dout <= 'd0 ;
    53. send_over <= 1'b0;
    54. rec_over <= 1'b0;
    55. sd_rc_flag <= 1'b0;
    56. spi_start<= 1'b0;
    57. spi_end <= 1'b0;
    58. end
    59. else begin
    60. case(state)
    61. idle:
    62. begin
    63. send_cnt<= 'd0 ;
    64. recv_cnt<= 'd0 ;
    65. dout <= 'd0 ;
    66. send_over <= 1'b0;
    67. rec_over <= 1'b0;
    68. sd_rc_flag <= 1'b0;
    69. spi_end <= 1'b0;
    70. if(cs_n == 0)begin
    71. state <= send_recv;
    72. spi_start<= 1'b1;
    73. end
    74. else begin
    75. state <= state;
    76. spi_start<= spi_start;
    77. end
    78. end
    79. send_recv: //send and receive
    80. begin
    81. if(cs_n == 1)begin
    82. state <= over;
    83. end
    84. else begin
    85. state <= state;
    86. end
    87. if(sd_rc_flag == 0 || sclk == 1)begin //发送
    88. miso <= din[DW-1-send_cnt];
    89. if(send_cnt < 3'b111)begin
    90. send_cnt <= send_cnt + 1 ;
    91. end
    92. else begin
    93. send_cnt <= 3'b000;
    94. end
    95. end
    96. else begin
    97. send_cnt <= send_cnt;
    98. end
    99. if(send_cnt == 3'b111 && sclk == 1)begin
    100. send_over <= 1'b1;
    101. end
    102. else begin
    103. send_over <= 1'b0;
    104. end
    105. ///
    106. if(sd_rc_flag == 1 && sclk == 0)begin //接收
    107. recv_data[DW-1-recv_cnt] <= mosi;
    108. if(recv_cnt < 3'b111)begin
    109. recv_cnt <= recv_cnt + 1;
    110. rec_over <= 1'b0;
    111. end
    112. else begin
    113. rec_over <= 1'b1;
    114. recv_cnt <= 3'b000;
    115. end
    116. end
    117. else begin
    118. recv_data<= recv_data;
    119. recv_cnt <= recv_cnt;
    120. rec_over <= rec_over;
    121. end
    122. if(recv_cnt == 3'b000 && sclk == 1)begin
    123. rec_over <= 1'b1;
    124. dout <= recv_data;
    125. end
    126. else begin
    127. rec_over <= 1'b0;
    128. dout <= 'd0;
    129. end
    130. spi_start<= 1'b0;
    131. spi_end <= 1'b0;
    132. sd_rc_flag <= 1'b1;
    133. end
    134. over:
    135. begin
    136. state <= idle;
    137. send_cnt <= 'd0 ;
    138. recv_cnt<= 'd0 ;
    139. dout <= 'd0 ;
    140. send_over <= 1'b0;
    141. rec_over <= 1'b0;
    142. sd_rc_flag <= 1'b0;
    143. spi_start<= 1'b0;
    144. spi_end <= 1'b1;
    145. end
    146. endcase
    147. end
    148. end
    149. endmodule

     主机仿真波形

    从机仿真波形

    Testbench源代码:

    1. `timescale 1ns / 1ps
    2. //
    3. // Company:
    4. // Engineer:
    5. //
    6. // Create Date: 2023/09/02 10:38:32
    7. // Design Name:
    8. // Module Name: tb_spi_derive
    9. // Project Name:
    10. // Target Devices:
    11. // Tool Versions:
    12. // Description:
    13. //
    14. // Dependencies:
    15. //
    16. // Revision:
    17. // Revision 0.01 - File Created
    18. // Additional Comments:
    19. //
    20. //
    21. module tb_spi_derive();
    22. reg sys_clk ;
    23. reg sys_rst ;
    24. reg[7:0] din ;
    25. reg dval_i ;
    26. reg spi_start ;
    27. reg spi_end ;
    28. wire[7:0] dout ;
    29. wire dval_o ;
    30. wire rec_over ;
    31. wire send_over ;
    32. wire sclk ;
    33. wire cs_n ;
    34. wire mosi ;
    35. wire miso ;
    36. reg[7:0] din_s ;
    37. wire spi_start_s ;
    38. wire spi_end_s ;
    39. wire[7:0] dout_s ;
    40. wire rec_over_s ;
    41. wire send_over_s ;
    42. reg[3:0] send_cnt ;
    43. reg[3:0] send_cnt_s ;
    44. SPI_derive #(
    45. .DW (8 )
    46. )
    47. u_SPI_derive(
    48. .sys_clk (sys_clk ) ,
    49. .sys_rst (sys_rst ) ,
    50. .din (din ),
    51. .spi_start (spi_start),
    52. .spi_end (spi_end ),
    53. .dout (dout ),
    54. .rec_over (rec_over ),
    55. .send_over (send_over),
    56. .sclk (sclk ),
    57. .cs_n (cs_n ),
    58. .mosi (mosi ),
    59. .miso (miso )
    60. );
    61. spi_slave #(
    62. .DW (8 )
    63. )
    64. u_spi_slave(
    65. .sys_clk (sys_clk ) ,
    66. .sys_rst (sys_rst ) ,
    67. .din (din_s ),
    68. .spi_start (spi_start_s),
    69. .spi_end (spi_end_s ),
    70. .dout (dout_s ),
    71. .rec_over (rec_over_s ),
    72. .send_over (send_over_s),
    73. .sclk (sclk ),
    74. .cs_n (cs_n ),
    75. .mosi (mosi ),
    76. .miso (miso )
    77. );
    78. initial begin
    79. sys_clk = 1;
    80. sys_rst = 0;
    81. spi_start = 1'b0;
    82. din = 8'd0;
    83. dval_i = 1'b0;
    84. spi_end = 1'b0;
    85. #80
    86. sys_rst = 1;
    87. din_s = 8'h51;
    88. #30
    89. spi_start = 1'b1; din = 8'b00110011;
    90. #20
    91. spi_start = 1'b0;
    92. end
    93. always@(posedge sys_clk,negedge sys_rst)
    94. begin
    95. if(!sys_rst)begin
    96. din <= 8'd0;
    97. spi_end <= 1'b0;
    98. send_cnt <= 4'b0;
    99. end
    100. else if(send_over == 1) begin
    101. din <= din + 8'b01;
    102. if(send_cnt < 10)begin
    103. send_cnt <= send_cnt + 1;
    104. spi_end <= 1'b0;
    105. end
    106. else begin
    107. send_cnt <= 4'b0;
    108. spi_end <= 1'b1;
    109. end
    110. end
    111. else begin
    112. spi_end <= 1'b0;
    113. send_cnt <= send_cnt;
    114. din <= din;
    115. end
    116. end
    117. always@(posedge sys_clk,negedge sys_rst)
    118. begin
    119. if(!sys_rst)begin
    120. din_s <= 8'b0;
    121. send_cnt_s<= 4'b0;
    122. end
    123. else if(spi_start_s == 1|| send_over_s ==1) begin
    124. din_s <= din_s + 8'b01;
    125. if(send_cnt_s < 10)begin
    126. send_cnt_s <= send_cnt_s + 1;
    127. end
    128. else begin
    129. send_cnt_s <= 4'b0;
    130. end
    131. end
    132. else begin
    133. send_cnt_s <= send_cnt_s;
    134. din_s <= din_s;
    135. end
    136. end
    137. initial begin
    138. forever #10 sys_clk = !sys_clk;
    139. end
    140. endmodule


    总结

    本文只是对SPI总线的模式0做初步实现,如有错误欢迎指正。

  • 相关阅读:
    redis 启动,关闭,查看状态
    Ubuntu安装freeSwitch
    如何通过API调用EasyPlayer.js播放器的视频实时录像功能?
    面试题-5
    数据结构笔记——树与二叉树
    飞利浦zigbee智能灯泡的软硬件设计
    鸿蒙HarmonyOS实战-ArkUI事件(键鼠事件)
    【JavaSE】继承和多态
    kafka安装与相关配置详解
    RD算法(一)—— 算法原理
  • 原文地址:https://blog.csdn.net/sqzjiayou/article/details/126686930