[米联客-安路飞龙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概述

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计数器、串并移位模块。

去毛刺:

信号在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模块:

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 
 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 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的控制确保采样时刻总线数据必然是稳定的。

 

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

 

 

 

 

2.2 程序源码

 

4 RTL仿真

4.1仿真激励文件

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

 

`timescale 1ns / 1ns//仿真时间刻度/精度

module uispi_rx#
(
parameter BITS_LEN = 8,
parameter CPOL = 1'b0,
parameter CPHA = 1'b0
)
(
input        I_clk,//系统时钟输入
input        I_rstn,//系统复位输入
input        I_spi_clk,//SPI时钟输入
input        I_spi_rx,//SPI rx数据输入
input        I_spi_ss,//SPI片选信号
output       O_spi_rvalid, //SPI rx 接收数据有效信号,当为1的时候spi_rdata数据有效
output [BITS_LEN-1'b1:0] O_spi_rdata//SPI rx接收到的数据输出
 );

reg  spi_cap   = 1'b0;
reg  [3:0]spi_clk_r = 4'd0;
reg  [4:0] spi_bit_cnt = 5'd0;
reg  [BITS_LEN-1'b1:0] spi_rx_r1;
reg  [3:0]spi_ss_r=4'd0;

wire spi_rx_en ;
wire spi_clkp ;
wire spi_clkn ;

assign O_spi_rdata  = spi_rx_r1;
assign O_spi_rvalid = (spi_bit_cnt == BITS_LEN);

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

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

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

always @(posedge I_clk or negedge I_rstn)begin
   if(I_rstn == 1'b0)
      spi_ss_r <= 4'd0;
   else
      spi_ss_r <= {spi_ss_r[2:0],I_spi_ss};                               //将I_spi_ss接收到的数据进行缓存
end
//当总线空闲时,当CPHA=1时,SCL=1;当CPHA=0时,则SCL=0
//CPOL用于控制第一时钟样本或第二时钟样本
//capture stroble 设置
always @(*)begin
      if(CPHA)begin 
         if(CPOL) spi_cap = spi_clkp;//CPHA=1  CPOL=1 
         else  spi_cap = spi_clkn;   //CPHA=1  CPOL=0
      end
      else begin 
         if(CPOL) spi_cap = spi_clkn;//CPHA=0  CPOL=1 
         else  spi_cap = spi_clkp;   //CPHA=0  CPOL=0    
      end
end

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

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

 

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

 

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

 

 

 

 

以下启动modelsim仿真

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

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

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

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

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

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

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

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

posted @ 2024-07-29 15:58  米联客(milianke)  阅读(22)  评论(0编辑  收藏  举报