当前位置:   article > 正文

[米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-15 SPI接收程序设计

[米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-15 SPI接收程序设计

软件版本:Anlogic -TD5.9.1-DR1_ES1.1

操作系统:WIN10 64bit

硬件平台:适用安路(Anlogic)FPGA

实验平台:米联客-MLK-L1-CZ06-DR1M90G开发板

板卡获取平台:https://milianke.tmall.com/

登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!

目录

1 概述

2 程序设计

2.1 SPI SLAVE接收驱动器设计

2.2 程序源码

3 RTL仿真

3.1 仿真激励文件

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

3.3 SPI发送驱动代码仿真CPHA=1 CPOL=0

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

3.5 SPI接收驱动代码仿真CPHA=1 CPOL=1


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的初始电平是高电平还是低电平。

程序设计

2.1 SPI SLAVE接收驱动器设计

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

去毛刺:

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

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

  1. //I_spi_clk去毛刺
  2. always @(posedge I_clk or negedge I_rstn)begin                       //SPI时钟信号,进行异步转同步处理
  3.    if(I_rstn == 1'b0)
  4.       spi_clk_r <= 4'd0;
  5.    else
  6.       spi_clk_r <= {spi_clk_r[2:0],I_spi_clk};
  7. end
  8. //I_spi_ss去毛刺
  9. always @(posedge I_clk or negedge I_rstn)begin
  10.    if(I_rstn == 1'b0)
  11.       spi_ss_r <= 4'd0;
  12.    else
  13.       spi_ss_r <= {spi_ss_r[2:0],I_spi_ss};                     //将I_spi_ss接收到的数据进行缓存
  14. end

SPI-CAP模块:

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

  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. //CPOL用于控制第一时钟样本或第二时钟样本
  4. //capture stroble 设置
  5. always @(*)begin
  6.       if(CPHA)begin 
  7.          if(CPOL) spi_cap = spi_clkp;//CPHA=1  CPOL=1
  8.          else  spi_cap = spi_clkn;   //CPHA=1  CPOL=0
  9.       end
  10.       else begin 
  11.          if(CPOL) spi_cap = spi_clkn;//CPHA=0  CPOL=1
  12.          else  spi_cap = spi_clkp;   //CPHA=0  CPOL=0    
  13.       end
  14. end

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

3 RTL仿真

3.1 仿真激励文件

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

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

  1. `timescale 1ns / 1ps
  2. module sim_top_tb();
  3. localparam BYTES = 8;
  4. localparam TCNT = BYTES*8*2-1;
  5. localparam CPOL = 0;
  6. localparam CPHA = 0;
  7. reg I_clk; //系统时钟
  8. reg [7:0] i;//计数器,用于产生SPI时钟数量
  9. reg I_rstn; //系统复位
  10. reg I_spi_clk;//SPI时钟
  11. reg I_spi_ss; //SPI的Slave选通信号
  12. reg [3:0]bit_cnt; //bit计数器
  13. reg [7:0]spi_tx_buf; //发送缓存(移位寄存器)
  14. reg [7:0]spi_tx_buf_r; //发送化缓存,用于产生测试数据
  15. reg first_data_flag; //是否一个时钟改变数据
  16. wire O_spi_rvalid; //SPI 数据接收有效,当该信号有效代表接收到一个有效数据
  17. wire [7:0]O_spi_rdata; //SPI读数据
  18. wire I_spi_rx;//SPI数据总线
  19. //tb模拟的SPI测试数据接到I_spi_rx
  20. assign I_spi_rx = spi_tx_buf[7];
  21. //例化SPI 接收模块
  22. uispi_rx#
  23. (
  24. .BITS_LEN(8),
  25. .CPOL(CPOL),
  26. .CPHA(CPHA)
  27. )
  28. I_spi_rxnst(
  29. .I_clk(I_clk),
  30. .I_rstn(I_rstn),
  31. .I_spi_clk(I_spi_clk),
  32. .I_spi_rx(I_spi_rx),
  33. .I_spi_ss(I_spi_ss),
  34. .O_spi_rvalid(O_spi_rvalid),
  35. .O_spi_rdata(O_spi_rdata)
  36. );
  37. initial begin
  38. I_clk = 1'b0;
  39. I_rstn = 1'b0;
  40. #100;
  41. I_rstn = 1'b1;
  42. end
  43. always #10 I_clk = ~I_clk; //时钟信号翻转,产生系统时钟
  44. initial begin
  45. #100;
  46. i = 0;
  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,代表SPI的SCLK默认是低电平,SPI接收器在SCLK第1个时钟沿采样。SPI发送驱动器数据在SCLK的第2个时钟沿更新,确保SPI下一个SCLK的第1个时钟沿数据有足够的建立和保持时间。下图以发送8’ha0为例。

3.3 SPI发送驱动代码仿真CPHA=1 CPOL=0

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

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

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

3.5 SPI接收驱动代码仿真CPHA=1 CPOL=1

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

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

闽ICP备14008679号