uisrc 发表于 2023-12-29 16:41:52

2-3-15 基于FPGA的SPI接收程序设计

软件版本:VIVADO2021.1操作系统:WIN10 64bit硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA登录米联客(MILIANKE)FPGA社区-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默认是高电平。2 SPI SLAVE接收驱动器设计SPI 接收驱动程序包含去毛刺采集、spi_cap stroble模块、bits counter计数器、串并移位模块。SPI接收控制器接收的数据和clk_i同步,当spi_rvalid为高电平的时候,数据有效。


3 SPI接收驱动程序设计我们先给出spi接收的驱动程序源码,然后对源码的设计做一些分析。
/*******************************SPI接收驱动*********************--以下是米联客设计的SPI接收驱动器--1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨--2.spi_rvalid有效,代表数据spi_rdata有效--3.SPI的时钟以及选通总线进行采样是异步采样,因此多次寄存消除亚稳态*********************************************************************/`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 O_spi_rdata//SPI rx接收到的数据输出 );
regspi_cap   = 1'b0;regspi_clk_r = 4'd0;reg spi_bit_cnt = 5'd0;reg spi_rx_r1;regspi_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==2'b01;                              //SPI时钟信号上升沿assign spi_clkn   = spi_clk_r==2'b10;                              //SPI时钟信号下降沿
assign spi_rx_en= (~spi_ss_r);                        //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,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,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=1CPOL=1         elsespi_cap = spi_clkn;   //CPHA=1CPOL=0      end      else begin         if(CPOL) spi_cap = spi_clkn;//CPHA=0CPOL=1         elsespi_cap = spi_clkp;   //CPHA=0CPOL=0          endend
//spi bit counteralways @(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 shiftalways @(posedge I_clk)begin   if(spi_rx_en&&spi_cap)                                       //spi_cap信号有效时,进行数据采样      spi_rx_r1 <= {spi_rx_r1,I_spi_rx};         //采样的数据进行移位,准备进行下次采样   else if(spi_rx_en == 1'b0)                                    //spi_rx_en拉低,采样结束      spi_rx_r1 <= 0;                                             //spi_rx_r1清零endEndmodule


我们看下驱动器的驱动程序部分核心模块设计分析3.1 去毛刺对SPI的时钟以及选通总线进行采样是异步采样,因此需要多次寄存消除亚稳态
always @(posedge I_clk or negedge I_rstn)begin    if(I_rstn == 1'b0)      spi_clk_r <= 4'd0;   else      spi_clk_r <= {spi_clk_r,I_spi_clk};//异步时钟消除亚稳态endalways @(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,I_spi_ss};//异步时钟数据打一拍,消除亚稳态end


3.2 CHPA和CPOL控制SPI_CAPspi_cap stroble信号用于控制数据的移位,根据CHPA和CPOL设置决定是时钟的上升沿,下降沿亦或者第一个时钟,或者第二个时钟采样。
//当总线空闲时,当CPHA=1时,SCL=1;当CPHA=0时,则SCL=0//CPOL用于控制第一时钟样本或第二时钟样本//capture stroble 设置always @(*)begin      if(CPHA)begin         if(CPOL) spi_cap = spi_clkp;//CPHA=1CPOL=1         elsespi_cap = spi_clkn;   //CPHA=1CPOL=0      end      else begin         if(CPOL) spi_cap = spi_clkn;//CPHA=0CPOL=1         elsespi_cap = spi_clkp;   //CPHA=0CPOL=0          endend


3.3 Bit Counter计数器Bit Counter计数器用于计数了多少bits的采样,对于SPI接收程序,我们增加了对任意单次传输长度的计算,可以支持不仅仅是8bit单字节的传输。
//spi bit counteralways @(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         


3.4 移位模块SPI接收移位模块,在每一个spi cap strobe有效的时候完成一次数据采样。这里并没有对spi的接收总线进行去除亚稳态处理,因为我们SPI采集可以通过CPHA 和CPOL的控制确保采样时刻总线数据必然是稳定的。
//spi bit shiftalways @(posedge I_clk)begin   if(spi_rx_en&&spi_cap)                                       //spi_cap信号有效时,进行数据采样      spi_rx_r1 <= {spi_rx_r1,I_spi_rx};         //采样的数据进行移位,准备进行下次采样   else if(spi_rx_en == 1'b0)                                    //spi_rx_en拉低,采样结束      spi_rx_r1 <= 0;                                             //spi_rx_r1清零end


4 RTL仿真4.1 仿真激励文件
`timescale 1ns / 1ps
module master_spi_tb;
localparamBYTES = 8;localparamTCNT= BYTES*8*2-1;
localparamCPOL = 1;localparamCPHA = 1;
reg I_clk; //系统时钟reg i;//计数器,用于产生SPI时钟数量reg I_rstn; //系统复位reg I_spi_clk;//SPI时钟reg I_spi_ss; //SPI的Slave选通信号reg bit_cnt; //bit计数器reg spi_tx_buf; //发送缓存(移位寄存器)reg spi_tx_buf_r; //发送化缓存,用于产生测试数据reg first_data_flag; //是否一个时钟改变数据
wire O_spi_rvalid; //SPI 数据接收有效,当该信号有效代表接收到一个有效数据wire O_spi_rdata; //SPI读数据wire I_spi_rx;//SPI数据总线
//tb模拟的SPI测试数据接到I_spi_rxassign I_spi_rx = spi_tx_buf;//例化SPI 接收模块uispi_rx#(.BITS_LEN(8),.CPOL(CPOL),.CPHA(CPHA))I_spi_rxnst(.I_clk(I_clk),.I_rstn(I_rstn),.I_spi_clk(I_spi_clk),.I_spi_rx(I_spi_rx),.I_spi_ss(I_spi_ss),.O_spi_rvalid(O_spi_rvalid),.O_spi_rdata(O_spi_rdata));initial begin    I_clk= 1'b0;    I_rstn = 1'b0;    #100;    I_rstn = 1'b1;end
always #10   I_clk= ~I_clk;   //时钟信号翻转,产生系统时钟
initial begin    #100;    i = 0;
    forever begin      I_spi_clk = CPOL; //设置时钟极性      I_spi_ss= 1; // 设置SPI的SS控制信号      #2000;      I_spi_ss= 0;      for(i=0;i<TCNT;i=i+1) #1000 I_spi_clk = ~ I_spi_clk; //产生SPI时钟      #2000;      I_spi_ss= 1;
    endend
initial begin      #100;      bit_cnt = 0;      first_data_flag =0;      spi_tx_buf = 8'ha0;      spi_tx_buf_r = 8'ha0;    forever begin
//spi ss 控件用于启用传输      wait(I_spi_ss);//spi ss      bit_cnt = 0;      spi_tx_buf = 8'ha0;      spi_tx_buf_r = 8'ha0;
      if((CPHA == 1 && CPOL ==0)||(CPHA == 1 && CPOL ==1))//第一个时钟沿改变数据的情况            first_data_flag = 1; //设置first_data_flag=1 下面的发送时序对应情况跳过第一个沿
//ss低时开始数据传输                wait(!I_spi_ss);
      while(!I_spi_ss)begin
//COPL=0 CPHA=0默认SCLK为低电平,对于发送方,在对于第1个bit数据提前放到总线            if(CPHA == 0 && CPOL ==0)begin             @(negedge I_spi_clk)begin //每个时钟的下降沿更新需要发送的BIT                if(bit_cnt == 7)begin//连续发送过程中,8bits 发送完毕后更新数据                  bit_cnt = 0;                  spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据                  spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器                end                else begin                  spi_tx_buf = {spi_tx_buf,1'b0};//数据移位,更新数据                  bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器                end             end            end
//CPHA=0 COPL=1 默认SCLK为高电平,对于发送方,在对于第1个bit数据提前放到总线            if(CPHA == 0 && CPOL ==1)begin             @(posedge I_spi_clk)begin //每个时钟的上升沿更新需要发送的BIT                if(bit_cnt == 7)begin //连续发送过程中,8bits 发送完毕后更新数据                  bit_cnt = 0;                  spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据                  spi_tx_buf = spi_tx_buf_r; //重新跟新发送寄存器                end                else begin                  spi_tx_buf = {spi_tx_buf,1'b0};//数据移位,更新数据                  bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器                end             end            end

//CPHA=1 COPL=0 默认SCLK为低电平,对于发送方,在第1个SCLK的跳变沿更新            if(CPHA == 1 && CPOL ==0)begin             @(posedge I_spi_clk)begin                if(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿                  first_data_flag = 0;                  //spi_tx_buf = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据                end                else begin                  if(bit_cnt == 7)begin                        bit_cnt = 0;                        spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据                        spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器                  end                  else begin                        spi_tx_buf = {spi_tx_buf,1'b0}; //数据移位,更新数据                        bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器                  end                end             end            end//CPHA=1 COPL=1 默认SCLK为高电平,对于发送方,在第1个SCLK的跳变沿更新            if(CPHA == 1 && CPOL ==1)begin             @(negedge I_spi_clk)begin                if(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿                  first_data_flag = 0;                  //spi_tx_buf = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据                end                else begin                  if(bit_cnt == 7)begin                        bit_cnt = 0;                        spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据                        spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器                  end                  else begin                        spi_tx_buf = {spi_tx_buf,1'b0};//数据移位,更新数据                        bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器                  end                end             end            end
      end    endend
endmodule



4.2 SPI接收驱动代码仿真CPHA=0 CPOL=0如下图所示,当CPHA=0 CPOL=0,代表SPI的SCLK默认是低电平,SPI接收器在SCLK第1个时钟沿采样。SPI发送驱动器数据在SCLK的第2个时钟沿更新,确保SPI下一个SCLK的第1个时钟沿数据有足够的建立和保持时间。下图以发送8'ha0为例。4.3 SPI发送驱动代码仿真CPHA=1 CPOL=0如下图所示,当CPHA=1 CPOL=0,代表SPI的SCLK默认是低电平,SPI接收器在SCLK第2个时钟沿采样。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取反
页: [1]
查看完整版本: 2-3-15 基于FPGA的SPI接收程序设计