[米联客-安路飞龙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的时钟以及选通总线进行采样是异步采样,我们采用多次寄存的方法消除亚稳态

//I_spi_clk去毛刺
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

//I_spi_ss去毛刺
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

SPI-CAP模块:

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

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


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

bits 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          

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 程序源码

`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 RTL仿真

3.1 仿真激励文件

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

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

`timescale 1ns / 1ps

module sim_top_tb();

localparam  BYTES = 8;
localparam  TCNT  = BYTES*8*2-1;

localparam  CPOL = 0;
localparam  CPHA = 0;

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

以下启动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取反

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/801168.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【漏洞复现】WordPress——Recall——SQL注入(CVE-2024-32709)

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 WordPress是一款免费开源的内容管理系统(CMS)&#xff0c;最初是…

Java 反射用法和8道练习题

目录 一、什么是反射二、反射的核心接口和类三、测试代码 Bean 类和目录结构Person 类代码目录结构 四、获取 Class 对象五、获取构造方法 Constructor 并使用六、获取成员变量 Field 并使用七、获取成员方法 Method 并使用八、练习1. 使用反射获取String类的所有公有方法&…

虚拟机:VMware功能,安装与使用

目录 一、虚拟机介绍 二、VMware 1.介绍 2.安装 &#xff08;1&#xff09;根据提示按步骤安装​编辑 &#xff08;2&#xff09;更改软件的安装地址​编辑 &#xff08;3&#xff09;根据自己的需求选择是否需要软件更新​编辑 &#xff08;4&#xff09;根据需求选择…

3. JavaSE ——【逻辑运算符】

&#x1f680; 开场白 亲爱的读者&#xff0c;大家好&#xff01;我是一名正在学习编程的高校生。在这个博客里&#xff0c;我将和大家一起探讨编程技巧、分享实用工具&#xff0c;并交流学习心得。希望通过我的博客&#xff0c;你能学到有用的知识&#xff0c;提高自己的技能&…

Billu_b0x靶机

信息收集 使用arp-scan 生成网络接口地址来查看ip 输入命令&#xff1a; arp-scan -l 可以查看到我们的目标ip为192.168.187.153 nmap扫描端口开放 输入命令&#xff1a; nmap -min-rate 10000 -p- 192.168.187.153 可以看到开放2个端口 nmap扫描端口信息 输入命令&…

【深度学习】PyTorch框架(2):激活函数

1.引言 在文中&#xff0c;我们将深入探讨流行的激活函数&#xff0c;并分析它们在神经网络优化特性中的作用。激活函数在深度学习模型中扮演着至关重要的角色&#xff0c;因为它们为网络引入了非线性特性。尽管文献中描述了众多的激活函数&#xff0c;但它们并非一视同仁&…

北京交通大学《深度学习》专业课,实验2-前馈神经网络

1. 源代码 见资源“北京交通大学《深度学习》专业课&#xff0c;实验2-前馈神经网络” 2. 实验内容 &#xff08;1&#xff09;手动实现前馈神经网络解决上述回归、二分类、多分类任务 分析实验结果并绘制训练集和测试集的loss曲线 &#xff08;2&#xff09;利用to…

发电机保护屏的工作原理和组成

发电机保护屏的工作原理和组成 发电机保护屏的工作原理是通过监测发电机的电气参数和运行状态&#xff0c;‌一旦发现异常或故障&#xff0c;‌及时采取相应的保护措施&#xff0c;‌以确保发电机的安全运行。‌ 发电机保护屏通常包含各种传感器、‌保护继电器和控制…

Golang | Leetcode Golang题解之第231题2的幂

题目&#xff1a; 题解&#xff1a; func isPowerOfTwo(n int) bool {const big 1 << 30return n > 0 && big%n 0 }

整数或小数点后补0操作

效果展示&#xff1a; 整数情况&#xff1a; 小数情况&#xff1a; 小编这里是以微信小程序举例&#xff0c;代码通用可兼容vue等。 1.在utils文件下创建工具util.js文本 util.js页面&#xff1a; // 格式…

docker desktop历史版本安装

1.安装choco Windows安装 choco包管理工具-CSDN博客 2.通过choco安装 下面例子为安装旧版2.3.0.2,其它版本类似 Chocolatey Software | Docker Desktop 2.3.0.2 https://download.docker.com/win/stable/45183/Docker%20Desktop%20Installer.exe choco install docker-des…

ESP32-S3多模态交互方案在线AI语音设备应用,启明云端乐鑫代理商

随着物联网&#xff08;IoT&#xff09;和人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;嵌入式设备正逐渐变得智能化&#xff0c;让我们的家庭生活变得更加智能化和个性化。 随着大型语言模型的不断进步和优化&#xff0c;AI语音机器人设备能够实现更加智能、…

绝缘子污秽comsol仿真参数

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

Java+springboot+vue智慧班牌小程序源码,智慧班牌系统可以提供哪些服务?

智慧班牌全套源码&#xff0c;系统技术架构&#xff1a;Javaspringbootvue element-ui小程序电子班牌&#xff1a;Java Android演示正版授权。 智慧班牌在智慧校园的数字化建设中提供了多种服务&#xff0c;这些服务不仅丰富了校园的信息展示方式&#xff0c;还促进了家校互动…

渲染100农场有哪些优势?渲染100邀请码1a12

渲染100是知名的渲染农场&#xff0c;深受广大设计师欢迎&#xff0c;比起其他农场&#xff0c;它有什么优势呢&#xff1f;我们一起来看看。 1、资源丰富 渲染100拥有强大的计算集群&#xff0c;能多线处理大规模、超复杂的场景渲染需要&#xff0c;性能卓越。2、成本低廉 渲…

文件的顺序读写

文件读写函数介绍 文件顺序读写函数 函数名功能适用于fputc 字符输出函数 所有输出流 fgetc 字符输⼊函数 所有输⼊流 fputs ⽂本⾏输出函数 所有输出流fgets ⽂本⾏输⼊函数 所有输⼊流fprintf 格式化输出函数 所有输出流fscanf 格式化输⼊函数 所有输⼊流fwrite ⼆进制输出…

Multi-modal Information Fusion for Action Unit Detection in the Wild

标题&#xff1a;多模态信息融合用于野外动作单元检测 源文链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2023W/ABAW/papers/Deng_Multi-Modal_Information_Fusion_for_Action_Unit_Detection_in_the_Wild_CVPRW_2023_paper.pdfhttps://openaccess.thecvf.com/…

【HarmonyOS开发】Tabs使用封装

背景 在写Tabs时&#xff0c;会使用很多个TabContent来实现不同页面的展示内容&#xff0c;但是如果TabContent数量很多时&#xff0c;会导致Tabs代码量大而且很臃肿&#xff0c;因此想着尝试去封装Tabs的使用&#xff0c;可以让界面整洁和对内容界面的解耦。 主要依托于wrap…

数据驱动下的私域运营战略布局

一、以用户为中心的组织重构或整合 发现&#xff0c;市场上大部分做的非常成功的私域项目&#xff0c;都是由CEO推动的、基于该战略的组织重构去驱动的。 我们也看到&#xff0c;在很多公司&#xff0c;私域运营是由品牌部门、CRM部门和Trade Marketing部门合作一起运营的。 …

SpringCloud教程 | 第十篇: 读取Nacos的配置

1、引入依赖 <dependency><groupId>com.alibaba.boot</groupId><artifactId>nacos-config-spring-boot-starter</artifactId><version>0.2.7</version></dependency> 2、在启动类加上 NacosPropertySource(dataId"nac…