[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA基础篇连载-15 SPI接收程序设计

文档创建者:FPGA课程
浏览次数:224
最后更新:2024-08-27
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 1-FPGA基础入门实验
本帖最后由 FPGA课程 于 2024-8-27 17:15 编辑

​ 软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!



1概述
SPI的接收器驱动程序主要为SPI_CLK和SPI_RX接收数据总线的时序来设计。通过前面的SPI协议学习,我们这里设计的SPI驱动程序需要支持CPHA=0 CPOL=0;CPHA=1 CPOL=0; CPHA=0 CPOL=1;CPHA=1 CPOL=1四种情况。CPHA用于控制SPI接收器的采样时钟位置,CPOL用于设置SPI_CLK的初始电平是高电平还是低电平。
SPI接收器的数据采集时序设计思路:
根据CPOL的设置,通过采集SPI_CLK的上升沿或者下降沿,产生spi_cap_stroble用于总线数据的采集
当CPHA=0的时候,SPI_CLK默认是低电平,当CPHA=1的时候SPI_CLK默认是高电平。
2SPI SLAVE接收驱动器设计
SPI 接收驱动程序包含去毛刺采集、spi_cap stroble模块、bits counter计数器、串并移位模块。
766cc36c9abf4452878ba2b7e415e60d.jpg
SPI接收控制器接收的数据和clk_i同步,当spi_rvalid为高电平的时候,数据有效。
3 SPI接收驱动程序设计
我们先给出spi接收的驱动程序源码,然后对源码的设计做一些分析。
  1. /*******************************SPI接收驱动*********************
  2. --以下是米联客设计的SPI接收驱动器
  3. --1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨
  4. --2.O_spi_rvalid有效,代表数据O_spi_rdata有效
  5. --3.SPI的时钟以及选通总线进行采样是异步采样,因此多次寄存消除亚稳态
  6. *********************************************************************/
  7. `timescale 1ns / 1ns//仿真时间刻度/精度

  8. module uispi_rx#
  9. (
  10. parameter BITS_LEN = 8,
  11. parameter CPOL = 1'b1,
  12. parameter CPHA = 1'b0
  13. )
  14. (
  15. input        I_sysclk_p,
  16. input        I_sysclk_n,//系统时钟输入
  17. input        I_rstn,//系统复位输入
  18. input        I_spi_clk,//SPI时钟输入
  19. input        I_spi_rx,//SPI rx数据输入
  20. input        I_spi_ss,//SPI片选信号
  21. output       O_spi_rvalid, //SPI rx 接收数据有效信号,当为1的时候O_spi_rdata数据有效
  22. output [BITS_LEN-1'b1:0] O_spi_rdata//SPI rx接收到的数据输出
  23. );

  24. reg  spi_cap   = 1'b0;
  25. reg  [3:0]spi_clk_r = 4'd0;
  26. reg  [4:0] spi_bit_cnt = 5'd0;
  27. reg  [BITS_LEN-1'b1:0] spi_rx_r1;
  28. reg  [3:0]spi_ss_r=4'd0;

  29. wire I_clk;
  30. IBUFGDS CLK_U(
  31. .I(I_sysclk_p),
  32. .IB(I_sysclk_n),
  33. .O(I_clk)
  34. );

  35. wire spi_rx_en ;
  36. wire spi_clkp ;
  37. wire spi_clkn ;

  38. assign O_spi_rdata  = spi_rx_r1;
  39. assign O_spi_rvalid = (spi_bit_cnt == BITS_LEN);

  40. assign spi_clkp   = spi_clk_r[3:2]==2'b01;                              //SPI时钟信号上升沿
  41. assign spi_clkn   = spi_clk_r[3:2]==2'b10;                              //SPI时钟信号下降沿

  42. assign spi_rx_en  = (~spi_ss_r[3]);                          //I_spi_ss片选信号持续拉低,使能拉高,接收启动

  43. always @(posedge I_clk or negedge I_rstn)begin                       //SPI时钟信号,进行异步转同步处理
  44.    if(I_rstn == 1'b0)
  45.       spi_clk_r <= 4'd0;
  46.    else
  47.       spi_clk_r <= {spi_clk_r[2:0],I_spi_clk};
  48. end

  49. always @(posedge I_clk or negedge I_rstn)begin
  50.    if(I_rstn == 1'b0)
  51.       spi_ss_r <= 4'd0;
  52.    else
  53.       spi_ss_r <= {spi_ss_r[2:0],I_spi_ss};                             //将I_spi_ss接收到的数据进行缓存
  54. end
  55. //当总线空闲时,当CPHA=1时,SCL=1;当CPHA=0时,则SCL=0
  56. //CPOL用于控制第一时钟样本或第二时钟样本
  57. //capture stroble 设置
  58. always @(*)begin
  59.       if(CPHA)begin
  60.          if(CPOL) spi_cap = spi_clkp;//CPHA=1  CPOL=1
  61.          else  spi_cap = spi_clkn;   //CPHA=1  CPOL=0
  62.       end
  63.       else begin
  64.          if(CPOL) spi_cap = spi_clkn;//CPHA=0  CPOL=1
  65.          else  spi_cap = spi_clkp;   //CPHA=0  CPOL=0   
  66.       end
  67. end

  68. //spi bit counter
  69. always @(posedge I_clk)begin
  70.     if(spi_rx_en&&spi_cap&&(spi_bit_cnt < BITS_LEN))       //计数到未到达参数BITS_LEN设定值
  71.        spi_bit_cnt <= spi_bit_cnt + 1'b1;                    //spi_bit_cnt计数器+1
  72.     else if(spi_rx_en==0||spi_bit_cnt == BITS_LEN)          //单次传输的长度由参数BITS_LEN来控制
  73.        spi_bit_cnt <= 0;                                        //计数到达设定值,计数清零
  74. end         

  75. //spi bit shift
  76. always @(posedge I_clk)begin
  77.      if(spi_rx_en&&spi_cap)                                         //spi_cap信号有效时,进行数据采样
  78.         spi_rx_r1 <= {spi_rx_r1[BITS_LEN-2:0],I_spi_rx};         //采样的数据进行移位,准备进行下次采样  
  79.      else if(spi_rx_en == 1'b0)                                    //spi_rx_en拉低,采样结束
  80.         spi_rx_r1 <= 0;                                             //spi_rx_r1清零
  81. end
  82. endmodule
复制代码


我们看下驱动器的驱动程序部分核心模块设计分析
3.1去毛刺
对SPI的时钟以及选通总线进行采样是异步采样,因此需要多次寄存消除亚稳态
  1. always @(posedge I_clk or negedge I_rstn)begin
  2.    if(I_rstn == 1'b0)
  3.       spi_clk_r <= 4'd0;
  4.    else
  5.      spi_clk_r <= {spi_clk_r[2:0],I_spi_clk};//异步时钟消除亚稳态
  6. end
  7. always @(posedge I_clk or negedge I_rstn)begin
  8.    if(I_rstn == 1'b0)
  9.       spi_ss_r <= 4'd0;
  10.    else
  11.       spi_ss_r <= {spi_ss_r[2:0],I_spi_ss};//异步时钟数据打一拍,消除亚稳态
  12. end
复制代码

3.2CPHA和CPOL控制SPI_CAP
spi_cap stroble信号用于控制数据的移位,根据CPHA和CPOL设置决定是时钟的上升沿,下降沿亦或者第一个时钟,或者第二个时钟采样。
  1. //当总线空闲时,当CPHA=1时,SCL=1;当CPHA=0时,则SCL=0
  2. //CPOL用于控制第一时钟样本或第二时钟样本
  3. //capture stroble 设置
  4. always @(*)begin
  5.       if(CPHA)begin
  6.          if(CPOL) spi_cap = spi_clkp;//CPHA=1  CPOL=1
  7.          else  spi_cap = spi_clkn;   //CPHA=1  CPOL=0
  8.       end
  9.       else begin
  10.          if(CPOL) spi_cap = spi_clkn;//CPHA=0  CPOL=1
  11.          else  spi_cap = spi_clkp;   //CPHA=0  CPOL=0   
  12.       end
  13. end
复制代码

3.3Bit Counter计数器
Bit Counter计数器用于计数了多少bits的采样,对于SPI接收程序,我们增加了对任意单次传输长度的计算,可以支持不仅仅是8bit单字节的传输。
  1. //当总线空闲时,当CPHA=1时,SCL=1;当CPHA=0时,则SCL=0
  2. //CPOL用于控制第一时钟样本或第二时钟样本
  3. //capture stroble 设置
  4. always @(*)begin
  5.       if(CPHA)begin
  6.          if(CPOL) spi_cap = spi_clkp;//CPHA=1  CPOL=1
  7.          else  spi_cap = spi_clkn;   //CPHA=1  CPOL=0
  8.       end
  9.       else begin
  10.          if(CPOL) spi_cap = spi_clkn;//CPHA=0  CPOL=1
  11.          else  spi_cap = spi_clkp;   //CPHA=0  CPOL=0   
  12.       end
  13. end
复制代码


3.4移位模块
SPI接收移位模块,在每一个spi cap strobe有效的时候完成一次数据采样。这里并没有对spi的接收总线进行去除亚稳态处理,因为我们SPI采集可以通过CPHA 和CPOL的控制确保采样时刻总线数据必然是稳定的。

  1. //spi bit shift
  2. always @(posedge I_clk)begin
  3.      if(spi_rx_en&&spi_cap)                                         //spi_cap信号有效时,进行数据采样
  4.         spi_rx_r1 <= {spi_rx_r1[BITS_LEN-2:0],I_spi_rx};         //采样的数据进行移位,准备进行下次采样  
  5.      else if(spi_rx_en == 1'b0)                                    //spi_rx_en拉低,采样结束
  6.         spi_rx_r1 <= 0;                                             //spi_rx_r1清零
  7. end
复制代码


4
RTL仿真
4.1仿真激励文件
  1. `timescale 1ns / 1ps

  2. module master_spi_tb;

  3. localparam  BYTES = 8;
  4. localparam  TCNT  = BYTES*8*2-1;
  5. localparam  CPOL = 1;
  6. localparam  CPHA = 0;

  7. reg I_sysclk_p;
  8. reg I_sysclk_n; //系统时钟
  9. reg [7:0] i;//计数器,用于产生SPI时钟数量
  10. reg I_rstn; //系统复位
  11. reg I_spi_clk;//SPI时钟
  12. reg I_spi_ss; //SPI的Slave选通信号
  13. reg [3:0]bit_cnt; //bit计数器
  14. reg [7:0]spi_tx_buf; //发送缓存(移位寄存器)
  15. reg [7:0]spi_tx_buf_r; //发送化缓存,用于产生测试数据
  16. reg first_data_flag; //是否一个时钟改变数据

  17. wire spi_rvalid; //SPI 数据接收有效,当该信号有效代表接收到一个有效数据
  18. wire [7:0]spi_rdata; //SPI读数据
  19. wire I_spi_rx;//SPI数据总线

  20. //tb模拟的SPI测试数据接到I_spi_rx
  21. assign I_spi_rx = spi_tx_buf[7];

  22. //例化SPI 接收模块
  23. uispi_rx#
  24. (
  25. .BITS_LEN(8),
  26. .CPOL(CPOL),
  27. .CPHA(CPHA)
  28. )
  29. I_spi_rxnst(
  30. .I_sysclk_p(I_sysclk_p),
  31. .I_sysclk_n(I_sysclk_n),
  32. .I_rstn(I_rstn),
  33. .I_spi_clk(I_spi_clk),
  34. .I_spi_rx(I_spi_rx),
  35. .I_spi_ss(I_spi_ss),
  36. .spi_rvalid(spi_rvalid),
  37. .spi_rdata(spi_rdata)
  38. );

  39. initial begin
  40.     I_sysclk_p  = 1'b0;
  41.     I_sysclk_n  = 1'b1;
  42.     I_rstn = 1'b0;
  43.     #100;
  44.     I_rstn = 1'b1;
  45. end

  46. always #10   I_sysclk_p  = ~I_sysclk_p;
  47. always #10   I_sysclk_n  = ~I_sysclk_n;   //时钟信号翻转,产生系统时钟

  48. initial begin
  49.     #100;
  50.     i = 0;
  51.    
  52.     forever begin
  53.         I_spi_clk = CPOL; //设置时钟极性
  54.         I_spi_ss  = 1; // 设置SPI的SS控制信号
  55.         #2000;
  56.         I_spi_ss  = 0;
  57.         for(i=0;i<TCNT;i=i+1) #1000 I_spi_clk = ~ I_spi_clk; //产生SPI时钟
  58.         #2000;
  59.         I_spi_ss  = 1;

  60.     end
  61. end

  62. initial begin
  63.         #100;
  64.         bit_cnt = 0;
  65.         first_data_flag =0;
  66.         spi_tx_buf[7:0] = 8'ha0;
  67.         spi_tx_buf_r[7:0] = 8'ha0;
  68.     forever begin
  69. //spi ss 控件用于启用传输
  70.         wait(I_spi_ss);//spi ss
  71.         bit_cnt = 0;
  72.         spi_tx_buf[7:0] = 8'ha0;
  73.         spi_tx_buf_r[7:0] = 8'ha0;

  74.         if((CPHA == 1 && CPOL ==0)||(CPHA == 1 && CPOL ==1))//第一个时钟沿改变数据的情况
  75.             first_data_flag = 1; //设置first_data_flag=1 下面的发送时序对应情况跳过第一个沿

  76. //ss低时开始数据传输         
  77.         wait(!I_spi_ss);

  78.         while(!I_spi_ss)begin

  79. //COPL=0 CPHA=0默认SCLK为低电平,对于发送方,在对于第1个bit数据提前放到总线
  80.             if(CPHA == 0 && CPOL ==0)begin
  81.              @(negedge I_spi_clk)  begin //每个时钟的下降沿更新需要发送的BIT
  82.                 if(bit_cnt == 7)begin//连续发送过程中,8bits 发送完毕后更新数据
  83.                     bit_cnt = 0;
  84.                     spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
  85.                     spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器
  86.                 end
  87.                 else begin
  88.                     spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据
  89.                     bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
  90.                 end
  91.              end
  92.             end

  93. //CPHA=0 COPL=1 默认SCLK为高电平,对于发送方,在对于第1个bit数据提前放到总线
  94.             if(CPHA == 0 && CPOL ==1)begin
  95.              @(posedge I_spi_clk)  begin //每个时钟的上升沿更新需要发送的BIT
  96.                 if(bit_cnt == 7)begin //连续发送过程中,8bits 发送完毕后更新数据
  97.                     bit_cnt = 0;
  98.                     spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
  99.                     spi_tx_buf = spi_tx_buf_r; //重新跟新发送寄存器
  100.                 end
  101.                 else begin
  102.                     spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据
  103.                     bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
  104.                 end
  105.              end
  106.             end

  107. //CPHA=1 COPL=0 默认SCLK为低电平,对于发送方,在第1个SCLK的跳变沿更新
  108.             if(CPHA == 1 && CPOL ==0)begin
  109.              @(posedge I_spi_clk)  begin
  110.                 if(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿
  111.                     first_data_flag = 0;
  112.                     //spi_tx_buf[7:0] = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据
  113.                 end
  114.                 else begin
  115.                     if(bit_cnt == 7)begin
  116.                         bit_cnt = 0;
  117.                         spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
  118.                         spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器
  119.                     end
  120.                     else begin
  121.                         spi_tx_buf = {spi_tx_buf[6:0],1'b0}; //数据移位,更新数据
  122.                         bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
  123.                     end
  124.                 end
  125.              end
  126.             end

  127. //CPHA=1 COPL=1 默认SCLK为高电平,对于发送方,在第1个SCLK的跳变沿更新
  128.             if(CPHA == 1 && CPOL ==1)begin
  129.              @(negedge I_spi_clk)  begin
  130.                 if(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿
  131.                     first_data_flag = 0;
  132.                     //spi_tx_buf[7:0] = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据
  133.                 end
  134.                 else begin
  135.                     if(bit_cnt == 7)begin
  136.                         bit_cnt = 0;
  137.                         spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
  138.                         spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器
  139.                     end
  140.                     else begin
  141.                         spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据
  142.                         bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
  143.                     end
  144.                 end
  145.              end
  146.             end

  147.         end
  148.     end
  149. end

  150. endmodule
复制代码

4.2SPI接收驱动代码仿真CPHA=0 CPOL=0
如下图所示,当CPHA=0 CPOL=0,代表SPI的SCLK默认是低电平,SPI接收器在SCLK第1个时钟沿采样。SPI发送驱动器数据在SCLK的第2个时钟沿更新,确保SPI下一个SCLK的第1个时钟沿数据有足够的建立和保持时间。下图以发送8’ha0为例。
205c8cdb436c4ed48c27065335b09f95.jpg
4.3SPI发送驱动代码仿真CPHA=1 CPOL=0
如下图所示,当CPHA=1 CPOL=0,代表SPI的SCLK默认是低电平,SPI接收器在SCLK第2个时钟沿采样。SPI发送驱动器数据在下一个SCLK的第1个时钟沿更新,确保SPI下一个SCLK的第2个时钟沿数据有足够的建立和保持时间。下图以发送8’h02为例。
cb85c81a27e04e66bc6f201865c02f38.jpg
4.4SPI接收驱动代码仿真CPHA=0 CPOL=1
和CPHA=0 CPOL=0这种设置相比,时钟SCLK取反
75daab1fb999497ba0e573c797cef02f.jpg


4.5SPI接收驱动代码仿真CPHA=1 CPOL=1
和CPHA=1 CPOL=0这种设置相比,时钟SCLK取反
17ea079dcdcd4444b4bca0ae95107cac.jpg



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则