首页 > 编程语言 >15 SPI接收程序设计

15 SPI接收程序设计

时间:2023-12-29 13:23:07浏览次数:44  
标签:begin 15 SPI end CPHA spi 程序设计 CPOL

软件版本: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 [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

我们看下驱动器的驱动程序部分核心模块设计分析

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[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};//异步时钟数据打一拍,消除亚稳态

end

3.2 CHPA和CPOL控制SPI_CAP

spi_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=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

3.3 Bit Counter计数器

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

//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          

3.4 移位模块

SPI接收移位模块,在每一个spi cap strobe有效的时候完成一次数据采样。这里并没有对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

4 RTL仿真

4.1 仿真激励文件

`timescale 1ns / 1ps

 

module master_spi_tb;

 

localparam  BYTES = 8;

localparam  TCNT  = BYTES*8*2-1;

 

localparam  CPOL = 1;

localparam  CPHA = 1;

 

reg I_clk; //系统时钟

reg [7:0] i;//计数器,用于产生SPI时钟数量

reg I_rstn; //系统复位

reg I_spi_clk;//SPI时钟

reg I_spi_ss; //SPI的Slave选通信号

reg [3:0]bit_cnt; //bit计数器

reg [7:0]spi_tx_buf; //发送缓存(移位寄存器)

reg [7:0]spi_tx_buf_r; //发送化缓存,用于产生测试数据

reg first_data_flag; //是否一个时钟改变数据

 

wire O_spi_rvalid; //SPI 数据接收有效,当该信号有效代表接收到一个有效数据

wire [7:0]O_spi_rdata; //SPI读数据

wire I_spi_rx;//SPI数据总线

 

//tb模拟的SPI测试数据接到I_spi_rx

assign I_spi_rx = spi_tx_buf[7];

//例化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;

 

    end

end

 

initial begin

        #100;

        bit_cnt = 0;

        first_data_flag =0;

        spi_tx_buf[7:0] = 8'ha0;

        spi_tx_buf_r[7:0] = 8'ha0;

    forever begin

 

//spi ss 控件用于启用传输

        wait(I_spi_ss);//spi ss

        bit_cnt = 0;

        spi_tx_buf[7:0] = 8'ha0;

        spi_tx_buf_r[7:0] = 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[6:0],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[6:0],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[7:0] = 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[6:0],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[7:0] = 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[6:0],1'b0};//数据移位,更新数据

                        bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器

                    end

                end

             end

            end

 

        end

    end

end

 

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取反

标签:begin,15,SPI,end,CPHA,spi,程序设计,CPOL
From: https://www.cnblogs.com/milianke/p/17934666.html

相关文章

  • 16 SPI LOOP环路实验
    软件版本:vitis2021.1(vivado2021.1)操作系统:WIN1064bit硬件平台:适用XILINXA7/K7/Z7/ZU/KU系列FPGA登录"米联客"FPGA社区-www.uisrc.com视频课程、答疑解惑!1概述在前面完成了SPI发送驱动程序、SPI接收驱动程序设计,本文对前面的SPI收发驱动做一个环路测试,通过仿真,以及在线仿......
  • 14 SPI MASET发送程序设计
    软件版本:VIVADO2021.1操作系统:WIN1064bit硬件平台:适用XILINXA7/K7/Z7/ZU/KU系列FPGA登录米联客(MILIANKE)FPGA社区-www.uisrc.com观看免费视频课程、在线答疑解惑!1概述SPI的发送器驱动程序主要围绕SPI_MOSI以及SPI_SCLK来设计。通过前面的SPI协议学习,我们这里设计的SPI驱......
  • 13 SPI通信协议原理
    软件版本:VIVADO2021.1操作系统:WIN1064bit硬件平台:适用XILINXA7/K7/Z7/ZU/KU系列FPGA登录米联客(MILIANKE)FPGA社区-www.uisrc.com观看免费视频课程、在线答疑解惑!1概述SPI是一种串行总线接口,也是各类嵌入式设备以及FPGA应用开发中常用的一种串行通信接口。SPI的接口速度......
  • H5前端特殊艺术字体文件太大,可通过font-spider压缩
    原理:1.爬行本地html文档,分析所有css语句2.记录@font-face语句声明的字体,并且记录使用该字体的css选择器3.通过css选择器的规则查找当前html文档的节点,记录节点上的文本4.找到字体文件并删除没被使用的字符5.编码成跨平台使用的字体格式简而言之:就是爬出你项目中......
  • 社招面试题:说一说SPI是什么,有哪些使用场景?
    大家好,我是小米!今天在这里和大家分享一个在技术面试中常被问到的话题——SPI(ServiceProviderInterface),这是一个令人着迷的技术领域,也是很多Java开发者必须要熟悉的概念。不废话,让我们一起来揭开SPI的神秘面纱,看看它在实际开发中有哪些精彩的应用场景吧!SPI是什么?首先,我们来解释一......
  • OpenEuler22.03SP2 LTS安装PostgreSQL15.5并配置一主二从
    环境准备序号IP标识(hostname)CPU/内存配置系统盘数据盘1192.168.8.190pg018C+16G80G500G2192.168.8.191pg028C+16G80G500G3192.168.8.192pg038C+16G80G500G规划的文件夹目录(所有主机):##以下目录为本文规划的目录,可以提前创建,也可以跟着......
  • AT_joisc2015_h 题解
    传送门题意:给定长为\(n\)的字符串\(s\),你可以选择一个区间,将区间内的字符从小到大排序,求可以得到的最长回文子串长度,字符集大小为\(n\)。很有意思的题目。首先容易做到\(O(n^3)\)。考虑怎么优化。我们先考察排序的区间和回文区间的关系。如果两个区间无交,那么显然排序......
  • 学期:2023-2024-1 学号:20231426 《计算机基础与程序设计》第十四周学习总结
    作业信息这个作业属于哪个课程2022-2023-1-计算机基础与程序设计这个作业要求在哪里2022-2023-1计算机基础与程序设计作业这个作业的目标通过教材内容了解文件,网络作业正文https://www.cnblogs.com/hhaxx/p/17933978.html教材学习内容总结《计算科学概论......
  • 2023-2024-1 20231307《计算机基础与程序设计》第十四周学习总结
    作业信息所属课程2023-2024-1-计算机基础与程序设计作业要求 第十四周作业(必学,选做)作业目标自学教材《C语言程序设计》第13章并完成实验作业正文https://www.cnblogs.com/lzt-/p/17933997.html教材学习内容总结13.1二进制文件和文本文件文本文件(也称ASCII......
  • 学期2023-2024-1 20231310 《计算机基础与程序设计》第十四周学习总结
    作业信息这个作业属于哪个课程2023-2024-1-计算机基础与程序设计这个作业要求在哪里2023-2024-1计算机基础与程序设计第十四周作业这个作业的目标《C语言程序设计》第13章并完成云班课测试作业正文https://www.cnblogs.com/wang-hoNbang/p/17933629.html教......