问答 店铺
热搜: ZYNQ FPGA discuz

QQ登录

只需一步,快速开始

微信登录

微信扫码,快速开始

微信扫一扫 分享朋友圈

已有 87 人浏览分享

开启左侧

[MILIANPAI-F01-EG4D]FPGA程序设计基础实验连载-15 SPI接收程序设计

[复制链接]
87 0
安路-FPGA课程
安路课程: 基础入门 » 新手入门实验
安路系列: EG4
本帖最后由 UT发布 于 2025-4-11 08:52 编辑

软件版本:TD_5.6.4_Release_97693
操作系统:WIN11 64bit
硬件平台:适用安路(Anlogic)FPGA
登录米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!
1 概述

SPI的接收器驱动程序主要为SPI_CLKSPI_RX接收数据总线的时序来设计。通过前面的SPI协议学习,我们这里设计的SPI驱动程序需要支持CPHA=0 CPOL=0;CPHA=1 CPOL=0; CPHA=0 CPOL=1; CPHA=1 CPOL=1四种情况。CPHA用于控制SPI接收器的采样时钟位置,CPOL用于设置SPI_CLK的初始电平是高电平还是低电平。

2 程序设计2.1 SPI SLAVE接收驱动器设计

SPI 接收驱动程序包含去毛刺采集、spi_cap stroble模块、bits counter计数器、串并移位模块。

image.jpg
去毛刺:

信号在FPGA内通过连线和逻辑单元时,都会产生延时。延时产生的原因:连线的长短和逻辑单元的数目;受器件的制造工艺、工作电压、温度等条件的影响,所以在信号变化的瞬间,组合逻辑的输出有先后顺序,信号到达端口的时间不一样这种状况成为“竞争”,一般在电气特性上表现为高频率的尖脉冲信号,这些信号称为毛刺。然而异步电路没办法做到真正意义上的毛刺消除,只能通过寄存器延迟转成同步电路才能处理毛刺问题。

SPI的时钟以及选通总线进行采样是异步采样,我们采用多次寄存的方法消除亚稳态


  1. always @(posedge I_clk or negedge I_rstn)begin                       //SPI时钟信号,进行异步转同步处理
  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};                               //将I_spi_ss接收到的数据进行缓存
  12. end
复制代码
SPI-CAP模块:

CHPACPOL控制spi_cap根据CHPACPOL设置决定是时钟的上升沿,下降沿亦或者第一个时钟,或者第二个时钟采样。

  1. assign spi_clkp   = spi_clk_r[3:2]==2'b01;                              //SPI时钟信号上升沿
  2. assign spi_clkn   = spi_clk_r[3:2]==2'b10;                              //SPI时钟信号下降沿
  3. //当总线空闲时,当CPHA=1时,SCL=1;当CPHA=0时,则SCL=0
  4. //CPOL用于控制第一时钟样本或第二时钟样本
  5. //capture stroble 设置
  6. always @(*)begin
  7.       if(CPHA)begin
  8.          if(CPOL) spi_cap = spi_clkp;//CPHA=1  CPOL=1
  9.          else  spi_cap = spi_clkn;   //CPHA=1  CPOL=0
  10.       end
  11.       else begin
  12.          if(CPOL) spi_cap = spi_clkn;//CPHA=0  CPOL=1
  13.          else  spi_cap = spi_clkp;   //CPHA=0  CPOL=0   
  14.       end
  15. en
复制代码
bits counter

Bit Counter计数器用于计数了多少bits的采样,对于SPI接收程序,我们增加了对任意单次传输长度的计算,可以支持不仅仅是8bit单字节的传输。

  1. //spi bit counter
  2. always @(posedge I_clk)begin
  3.     if(spi_rx_en&&spi_cap&&(spi_bit_cnt < BITS_LEN))       //计数到未到达参数BITS_LEN设定值
  4.        spi_bit_cnt <= spi_bit_cnt + 1'b1;                    //spi_bit_cnt计数器+1
  5.     else if(spi_rx_en==0||spi_bit_cnt == BITS_LEN)          //单次传输的长度由参数BITS_LEN来控制
  6.        spi_bit_cnt <= 0;                                        //计数到达设定值,计数清零
  7. end      
复制代码
移位模块:

SPI接收移位模块,在每一个spi cap 有效的时候完成一次数据采样。这里并没有对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
复制代码
2.2 程序源码
  1. module uispi_rx#
  2. (
  3. parameter BITS_LEN = 8,
  4. parameter CPOL = 1'b0,
  5. parameter CPHA = 1'b1
  6. )
  7. (
  8. input        I_clk,//系统时钟输入
  9. input        I_rstn,//系统复位输入
  10. input        I_spi_clk,//SPI时钟输入
  11. input        I_spi_rx,//SPI rx数据输入
  12. input        I_spi_ss,//SPI片选信号
  13. output       O_spi_rvalid, //SPI rx 接收数据有效信号,当为1的时候spi_rdata数据有效
  14. output [BITS_LEN-1'b1:0] O_spi_rdata//SPI rx接收到的数据输出
  15. );
  16. reg  spi_cap   = 1'b0;
  17. reg  [3:0]spi_clk_r = 4'd0;
  18. reg  [4:0] spi_bit_cnt = 5'd0;
  19. reg  [BITS_LEN-1'b1:0] spi_rx_r1;
  20. reg  [3:0]spi_ss_r=4'd0;
  21. wire spi_rx_en ;
  22. wire spi_clkp ;
  23. wire spi_clkn ;
  24. assign O_spi_rdata  = spi_rx_r1;
  25. assign O_spi_rvalid = (spi_bit_cnt == BITS_LEN);
  26. assign spi_clkp   = spi_clk_r[3:2]==2'b01;                              //SPI时钟信号上升沿
  27. assign spi_clkn   = spi_clk_r[3:2]==2'b10;                              //SPI时钟信号下降沿
  28. assign spi_rx_en  = (~spi_ss_r[3]);                          //I_spi_ss片选信号持续拉低,使能拉高,接收启动
  29. always @(posedge I_clk or negedge I_rstn)begin                       //SPI时钟信号,进行异步转同步处理
  30.    if(I_rstn == 1'b0)
  31.       spi_clk_r <= 4'd0;
  32.    else
  33.       spi_clk_r <= {spi_clk_r[2:0],I_spi_clk};
  34. end
  35. always @(posedge I_clk or negedge I_rstn)begin
  36.    if(I_rstn == 1'b0)
  37.       spi_ss_r <= 4'd0;
  38.    else
  39.       spi_ss_r <= {spi_ss_r[2:0],I_spi_ss};                               //将I_spi_ss接收到的数据进行缓存
  40. end
  41. //当总线空闲时,当CPHA=1时,SCL=1;当CPHA=0时,则SCL=0
  42. //CPOL用于控制第一时钟样本或第二时钟样本
  43. //capture stroble 设置
  44. always @(*)begin
  45.       if(CPHA)begin
  46.          if(CPOL) spi_cap = spi_clkp;//CPHA=1  CPOL=1
  47.          else  spi_cap = spi_clkn;   //CPHA=1  CPOL=0
  48.       end
  49.       else begin
  50.          if(CPOL) spi_cap = spi_clkn;//CPHA=0  CPOL=1
  51.          else  spi_cap = spi_clkp;   //CPHA=0  CPOL=0   
  52.       end
  53. end
  54. //spi bit counter
  55. always @(posedge I_clk)begin
  56.     if(spi_rx_en&&spi_cap&&(spi_bit_cnt < BITS_LEN))       //计数到未到达参数BITS_LEN设定值
  57.        spi_bit_cnt <= spi_bit_cnt + 1'b1;                    //spi_bit_cnt计数器+1
  58.     else if(spi_rx_en==0||spi_bit_cnt == BITS_LEN)          //单次传输的长度由参数BITS_LEN来控制
  59.        spi_bit_cnt <= 0;                                        //计数到达设定值,计数清零
  60. end         
  61. //spi bit shift
  62. always @(posedge I_clk)begin
  63.      if(spi_rx_en&&spi_cap)                                         //spi_cap信号有效时,进行数据采样
  64.         spi_rx_r1 <= {spi_rx_r1[BITS_LEN-2:0],I_spi_rx};         //采样的数据进行移位,准备进行下次采样  
  65.      else if(spi_rx_en == 1'b0)                                    //spi_rx_en拉低,采样结束
  66.         spi_rx_r1 <= 0;                                             //spi_rx_r1清零
  67. end
  68. endmodule
复制代码
3 RTL仿真
3.1仿真激励文件

Modelsim仿真的创建过程不再重复,如有不清楚的请看前面实验

本实验以仿真的方式演示,仿真激励信号提供一个系统时钟即可

  1. module master_spi_tb;
  2. localparam  BYTES = 8;
  3. localparam  TCNT  = BYTES*8*2-1;
  4. localparam  CPOL = 0;
  5. localparam  CPHA = 0;
  6. reg I_clk; //系统时钟
  7. reg [7:0] i;//计数器,用于产生SPI时钟数量
  8. reg I_rstn; //系统复位
  9. reg I_spi_clk;//SPI时钟
  10. reg I_spi_ss; //SPI的Slave选通信号
  11. reg [3:0]bit_cnt; //bit计数器
  12. reg [7:0]spi_tx_buf; //发送缓存(移位寄存器)
  13. reg [7:0]spi_tx_buf_r; //发送化缓存,用于产生测试数据
  14. reg first_data_flag; //是否一个时钟改变数据
  15. wire O_spi_rvalid; //SPI 数据接收有效,当该信号有效代表接收到一个有效数据
  16. wire [7:0]O_spi_rdata; //SPI读数据
  17. wire I_spi_rx;//SPI数据总线
  18. //tb模拟的SPI测试数据接到I_spi_rx
  19. assign I_spi_rx = spi_tx_buf[7];
  20. //例化SPI 接收模块
  21. uispi_rx#
  22. (
  23. .BITS_LEN(8),
  24. .CPOL(CPOL),
  25. .CPHA(CPHA)
  26. )
  27. I_spi_rxnst(
  28. .I_clk(I_clk),
  29. .I_rstn(I_rstn),
  30. .I_spi_clk(I_spi_clk),
  31. .I_spi_rx(I_spi_rx),
  32. .I_spi_ss(I_spi_ss),
  33. .O_spi_rvalid(O_spi_rvalid),
  34. .O_spi_rdata(O_spi_rdata)
  35. );
  36. initial begin
  37.     I_clk  = 1'b0;
  38.     I_rstn = 1'b0;
  39.     #100;
  40.     I_rstn = 1'b1;
  41. end
  42. always #10   I_clk  = ~I_clk;   //时钟信号翻转,产生系统时钟
  43. initial begin
  44.     #100;
  45.     i = 0;
  46.    
  47.     forever begin
  48.         I_spi_clk = CPOL; //设置时钟极性
  49.         I_spi_ss  = 1; // 设置SPI的SS控制信号
  50.         #2000;
  51.         I_spi_ss  = 0;
  52.         for(i=0;i<TCNT;i=i+1) #1000 I_spi_clk = ~ I_spi_clk; //产生SPI时钟
  53.         #2000;
  54.         I_spi_ss  = 1;
  55.     end
  56. end
  57. initial begin
  58.         #100;
  59.         bit_cnt = 0;
  60.         first_data_flag =0;
  61.         spi_tx_buf[7:0] = 8'ha0;
  62.         spi_tx_buf_r[7:0] = 8'ha0;
  63.     forever begin
  64. //spi ss 控件用于启用传输
  65.         wait(I_spi_ss);//spi ss
  66.         bit_cnt = 0;
  67.         spi_tx_buf[7:0] = 8'ha0;
  68.         spi_tx_buf_r[7:0] = 8'ha0;
  69.         if((CPHA == 1 && CPOL ==0)||(CPHA == 1 && CPOL ==1))//第一个时钟沿改变数据的情况
  70.             first_data_flag = 1; //设置first_data_flag=1 下面的发送时序对应情况跳过第一个沿
  71. //ss低时开始数据传输         
  72.         wait(!I_spi_ss);
  73.         while(!I_spi_ss)begin
  74. //COPL=0 CPHA=0默认SCLK为低电平,对于发送方,在对于第1个bit数据提前放到总线
  75.             if(CPHA == 0 && CPOL ==0)begin
  76.              @(negedge I_spi_clk)  begin //每个时钟的下降沿更新需要发送的BIT
  77.                 if(bit_cnt == 7)begin//连续发送过程中,8bits 发送完毕后更新数据
  78.                     bit_cnt = 0;
  79.                     spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
  80.                     spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器
  81.                 end
  82.                 else begin
  83.                     spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据
  84.                     bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
  85.                 end
  86.              end
  87.             end
  88. //CPHA=0 COPL=1 默认SCLK为高电平,对于发送方,在对于第1个bit数据提前放到总线
  89.             if(CPHA == 0 && CPOL ==1)begin
  90.              @(posedge I_spi_clk)  begin //每个时钟的上升沿更新需要发送的BIT
  91.                 if(bit_cnt == 7)begin //连续发送过程中,8bits 发送完毕后更新数据
  92.                     bit_cnt = 0;
  93.                     spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
  94.                     spi_tx_buf = spi_tx_buf_r; //重新跟新发送寄存器
  95.                 end
  96.                 else begin
  97.                     spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据
  98.                     bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
  99.                 end
  100.              end
  101.             end
  102. //CPHA=1 COPL=0 默认SCLK为低电平,对于发送方,在第1个SCLK的跳变沿更新
  103.             if(CPHA == 1 && CPOL ==0)begin
  104.              @(posedge I_spi_clk)  begin
  105.                 if(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿
  106.                     first_data_flag = 0;
  107.                     //spi_tx_buf[7:0] = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据
  108.                 end
  109.                 else begin
  110.                     if(bit_cnt == 7)begin
  111.                         bit_cnt = 0;
  112.                         spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
  113.                         spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器
  114.                     end
  115.                     else begin
  116.                         spi_tx_buf = {spi_tx_buf[6:0],1'b0}; //数据移位,更新数据
  117.                         bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
  118.                     end
  119.                 end
  120.              end
  121.             end
  122. //CPHA=1 COPL=1 默认SCLK为高电平,对于发送方,在第1个SCLK的跳变沿更新
  123.             if(CPHA == 1 && CPOL ==1)begin
  124.              @(negedge I_spi_clk)  begin
  125.                 if(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿
  126.                     first_data_flag = 0;
  127.                     //spi_tx_buf[7:0] = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据
  128.                 end
  129.                 else begin
  130.                     if(bit_cnt == 7)begin
  131.                         bit_cnt = 0;
  132.                         spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据
  133.                         spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器
  134.                     end
  135.                     else begin
  136.                         spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据
  137.                         bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器
  138.                     end
  139.                 end
  140.              end
  141.             end
  142.         end
  143.     end
  144. end
  145. endmodule
复制代码

以下启动modelsim仿真

3.2 SPI接收驱动代码仿真CPHA=0 CPOL=0

如下图所示,当CPHA=0 CPOL=0,代表SPISCLK默认是低电平,SPI接收器在SCLK1个时钟沿采样。SPI发送驱动器数据在SCLK的第2个时钟沿更新,确保SPI下一个SCLK的第1个时钟沿数据有足够的建立和保持时间。下图以发送8’ha0为例。

image.jpg
3.3 SPI接收驱动代码仿真CPHA=1 CPOL=0

如下图所示,当CPHA=1 CPOL=0,代表SPISCLK默认是低电平,SPI接收器在SCLK2个时钟沿采样。SPI发送驱动器数据在下一个SCLK的第1个时钟沿更新,确保SPI下一个SCLK的第2个时钟沿数据有足够的建立和保持时间。下图以发送8’ha0为例。

image.jpg
3.4 SPI发送驱动代码仿真CPHA=0 CPOL=1

CPHA=0 CPOL=0这种设置相比,时钟SCLK取反

image.jpg
3.5 SPI发送驱动代码仿真CPHA=1 CPOL=1

CPHA=1 CPOL=0这种设置相比,时钟SCLK取反

image.jpg







v





























































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

本版积分规则

0

关注

0

粉丝

293

主题
精彩推荐
热门资讯
网友晒图
图文推荐

  • 微信公众平台

  • 扫描访问手机版