SPI通信——FPGA学习笔记14

一、简介

        SPI(Serial Periphera Interface,串行外围设备接口)通讯协议,是 Motorola 公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输,广泛用于 EEPROM、FIash、RTC(实时时钟)、ADC(数模转换器)、DSP(数字信号处理器)以及数字信号解码器上,是常用的、重要的低速通讯协议之一。        

多从机

常规寻址:

通信过程:

SPI协议

时钟极性(CPOL):空闲时时钟的电平极性

时钟相位(CPHA):控制数据更新时刻和采样时刻

CPOL=0,CPHA=0

Sendstrobe:内部发送模块发送的触发信号;Capstrobe:内部接收数据模块采样的触发信号

CPOL=0,CPHA=1

CPOL=1,CPHA=0

 CPOL=1,CPHA=1

 

驱动基本模块

FPGA时序要求

二、程序设计

1、本实验设计一个SPI发送驱动,包含SPI四种工作模式

2、握手信号

        只有当req请求信号拉高并低电平时才会开启一次数据传输,数据传输过程中busy信号拉高表示设备忙。

3、SPI发送状态机控制器设计

`timescale 1ns / 1psmodule spi_tx#(parameter CLK_DIV   = 'd100 ,   //     parameter CPOL      = 1'b0  ,   //时钟极性parameter CPHA      = 1'b0      //时钟相位
)
(   input           I_clk           ,   //系统时钟input           I_rstn          ,   //系统复位input           I_spi_tx_req    ,   //发送数据请求input   [7:0]   I_spi_tx_data   ,   //发送数据output          O_spi_mosi      ,   //输出SPI数据output          O_spi_sclk      ,   //输出SPI时钟output          O_spi_busy          //输出忙信号);localparam  [9:0]   SPI_DIV     =   CLK_DIV     ;   //第二时钟边沿计数器
localparam  [9:0]   SPI_DIV1    =   SPI_DIV / 2 ;   //第一时钟边沿计数器    //分频系数一半reg [9:0]   clk_div         ;
reg         spi_en          ;       //发送使能
reg [3:0]   tx_cnt          ;
reg         spi_clk         ;
reg [7:0]   I_spi_tx_data_r ;  
reg         spi_strobe_en   ; wire        clk_end         ;   //
wire        clk_en1         ;   //第一内部时钟使能
wire        clk_en2         ;   //第二内部时钟使能
wire        spi_strobe      ;//计数器发送第一个时钟0-7次,当计数达到8时,不发送时钟
assign      clk_en1     = (clk_div == SPI_DIV1);    //第一内部时钟边沿使能      一半
assign      clk_en2     = (clk_div == SPI_DIV );    //第二内部时钟边沿使能      记满
assign      clk_end     = ((clk_div == SPI_DIV1)&&(tx_cnt == 4'd8));    //时钟结束信号    计数半个周期//当CPHA=0时,数据的第一个SCLK转换边缘被采样,因此数据更新在第二个转换边缘上
//当CPHA=1时,数据的第二个SCLK转换边缘被采样,因此数据更新在第一个转换边缘上
assign      spi_strobe = CPHA ?  clk_en1 & spi_strobe_en : clk_en2 & spi_strobe_en;
assign      O_spi_sclk  = (CPOL == 1'b1) ? !spi_clk : spi_clk ;         //设置SPI初始时钟
assign      O_spi_mosi  = I_spi_tx_data_r[7] ;                          //SPI输出数据
assign      O_spi_busy  = spi_en;//SPI时钟计数器
always @(posedge I_clk ) beginif (spi_en == 1'b0) beginclk_div <= 10'd0;end else if(clk_div < SPI_DIV)beginclk_div <= clk_div + 1'b1;endelse beginclk_div <= 10'd0;end
end//SPI时钟生成
always @(posedge I_clk ) beginif (spi_en == 1'b0) beginspi_clk <= 1'b0;    end else if(clk_en2 == 1'b1)beginspi_clk <= 1'b0;endelse if ((clk_en1 == 1'b1)&&(tx_cnt < 4'd8)) beginspi_clk <= 1'b1;end else beginspi_clk <= spi_clk;    end
end//SPI bit计数器
always @(posedge I_clk ) beginif ((!I_rstn)||(spi_en == 1'b0)) begintx_cnt <= 4'd0;end else if(clk_en1 == 1'b1)begintx_cnt <= tx_cnt + 1'b1;end
end//
always @(posedge I_clk ) beginif (!I_rstn) beginspi_strobe_en <= 1'b0;end else if(tx_cnt < 4'd8)beginif (clk_en1 == 1'b1) beginspi_strobe_en <= 1'b1;end else beginspi_strobe_en <= spi_strobe_en; end    endelse beginspi_strobe_en <= 1'b0;end
end//SPI发送模块
always @(posedge I_clk ) beginif ((!I_rstn)||(clk_end == 1'b1)) beginspi_en <= 1'b0;I_spi_tx_data_r <= 8'b0;end else if((I_spi_tx_req == 1'b1)&&(spi_en == 1'b0))begin      //启动传输spi_en <= 1'b1;I_spi_tx_data_r <=  I_spi_tx_data;endelse if (spi_en == 1'b1) beginI_spi_tx_data_r[7:0] <= (spi_strobe)?{I_spi_tx_data_r[6:0],1'b1} : I_spi_tx_data_r;end
endendmodule
`timescale 1ns / 1psmodule spi_master_tx#(parameter   CLK_DIV = 100
)
(input   I_clk       ,   //input   I_rstn      ,   //output  O_spi_sclk  ,   //output  O_spi_mosi      //
);wire        spi_busy    ;   //spi忙的标志
reg         spi_tx_req  ;   //spi请求发送
reg [7:0]   spi_tx_data ;   //spi待发送数据
reg [1:0]   M_S         ;   //状态机always @(posedge I_clk ) beginif (!I_rstn) beginspi_tx_req  <=  1'b0;spi_tx_data <=  8'd0;M_S         <=  2'd0;end else begincase (M_S)0:beginif (spi_busy == 1'b0) beginspi_tx_req   <= 1'b1;       //请求发送spi_tx_data  <= spi_tx_data + 1'b1;     //测试累加数据M_S          <= 2'd1; end end 1:beginif (spi_busy == 1'b1) begin     //清楚请求信号spi_tx_req <= 1'b0;M_S        <= 2'd0;endenddefault:M_S        <= 2'd0;endcase    end
endspi_tx#(.CLK_DIV   (CLK_DIV),   //     .CPOL      (1'b0   ),   //时钟极性.CPHA      (1'b0   )    //时钟相位
)
u_spi_tx(   .I_clk           (I_clk         ),   //系统时钟.I_rstn          (I_rstn        ),   //系统复位.I_spi_tx_req    (spi_tx_req    ),   //发送数据请求.I_spi_tx_data   (spi_tx_data   ),   //发送数据.O_spi_mosi      (O_spi_mosi    ),   //输出SPI数据.O_spi_sclk      (O_spi_sclk    ),   //输出SPI时钟.O_spi_busy      (spi_busy      )    //输出忙信号
);endmodule

 

`timescale 1ns / 1psmodule tb( );localparam  SYS_TIME = 4'd10;reg I_clk;
reg I_rstn;wire    O_spi_mosi;
wire    O_spi_sclk;spi_master_tx#(.CLK_DIV (100)
)
u_spi_master_tx(.I_clk       (I_clk         ),   //.I_rstn      (I_rstn        ),   //.O_spi_sclk  (O_spi_sclk    ),   //.O_spi_mosi  (O_spi_mosi    )    //
);initial beginI_clk = 0;I_rstn = 0;#100;I_rstn = 1;
endalways #(SYS_TIME / 2) I_clk = !I_clk;endmodule

 00

4、SPI接收模块

 

`timescale 1ns / 1psmodule spi_rx#
(parameter   BITS_LEM    =   8       ,//bit数量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:rdata有效     0:无效output [BITS_LEM - 1'b1:0]  O_spi_rdata     //SPI rx接收到的数据输出);reg [3:0]               spi_clk_r   ;       //时钟打拍
reg [3:0]               spi_ss_r    ;       //片选打拍
reg                     spi_cap     ;       //采样信号
reg [3:0]               spi_bit_cnt ;       //bit计数器
reg [BITS_LEM - 1'b1:0] spi_rx_r1   ;       //接收缓存wire                    spi_rx_en   ;       //接收使能
wire                    spi_clkp    ;       //spi上升沿
wire                    spi_clkn    ;       //spi下升沿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])              ;assign  O_spi_rdata     =   spi_rx_r1                   ;
assign  O_spi_rvalid    =   (spi_bit_cnt == BITS_LEM)   ;//I_spi_clk去毛刺
always @(posedge I_clk or negedge I_rstn ) beginif (!I_rstn) beginspi_clk_r <= 1'b0;end else beginspi_clk_r <= {spi_clk_r[2:0],I_spi_clk};     end
end//I_spi_ss去毛刺
always @(posedge I_clk or negedge I_rstn ) beginif (!I_rstn) beginspi_ss_r <= 1'b0;end else beginspi_ss_r <= {spi_ss_r[2:0],I_spi_ss};     end
end//cap信号生成    何时采样
always @( * ) beginif (CPHA == 1'b1) beginif (CPOL == 1'b1) beginspi_cap = spi_clkn;        //CPHA = 1   CPOL = 1end else beginspi_cap = spi_clkp;        //CPHA = 1   CPOL = 0   endend else beginif (CPOL == 1'b1) beginspi_cap = spi_clkn;         //CPHA = 0   CPOL = 1 end else beginspi_cap = spi_clkp;         //CPHA = 0   CPOL = 0 endend
end//bit计数器
always @(posedge I_clk ) beginif ((spi_rx_en == 1'b1)&&(spi_bit_cnt < BITS_LEM)&&(spi_cap == 1'b1)) begin    //开启接收,并且未达到计数最大值spi_bit_cnt <= spi_bit_cnt + 1'b1;end else if((spi_rx_en == 1'b0)||(spi_bit_cnt == BITS_LEM))begin    //未开启接收    或计数到最大追spi_bit_cnt <= 4'd0;end
end//bit移位  串转并
always @(posedge I_clk ) beginif ((spi_rx_en == 1'b1)&&(spi_cap == 1'b1)) beginspi_rx_r1 <= {spi_rx_r1[BITS_LEM - 2:0],I_spi_rx};          //移位寄存   高位在前end else if(spi_rx_en == 1'b0)beginspi_rx_r1 <= 'd0;end
endendmodule
`timescale 1ns / 1psmodule tb( );localparam   BYTES       =   8       ;
localparam   CPOL        =   1'b0    ;
localparam   CPHA        =   1'b0    ;localparam   TCNT        =   BYTES * 8 * 2 - 1;reg             I_clk               ;       //系统时钟
reg     [7:0]   i                   ;       //计数器,产生SPI时钟           
reg             I_rstn              ;       //系统复位
reg             I_spi_clk           ;       //SPI时钟
reg             I_spi_ss            ;       //SPI片选
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         ;       
wire            I_spi_rx            ;  assign  I_spi_rx = spi_tx_buf[7];spi_rx#
(.BITS_LEM    (8      ) ,//bit数量.CPOL        (1'b0   ) ,.CPHA        (1'b0   )  
)
u_spi_rx(   .I_clk       (I_clk         ),   //系统时钟.I_rstn      (I_rstn        ),   //系统复位.I_spi_clk   (I_spi_clk     ),   //SPI时钟.I_spi_rx    (I_spi_rx      ),   //SPI rx数据总线.I_spi_ss    (I_spi_ss      ),   //SPI片选信号.O_spi_rvalid(O_spi_rvalid  ),   //SPI rx接收数据有效信号  1:rdata有效     0:无效.O_spi_rdata (O_spi_rdata   )    //SPI rx接收到的数据输出
);initial beginI_clk = 0;I_rstn = 0;#100;I_rstn = 1;
endalways #5 I_clk = !I_clk;initial begin#100i = 0;forever beginI_spi_clk = CPOL;I_spi_ss = 1;#2000I_spi_ss = 0;for (i = 0;i<TCNT ;i = i + 1 ) begin#1000I_spi_clk = !I_spi_clk;end#2000I_spi_ss = 1;end
endinitial begin#100bit_cnt = 0;first_data_flag = 0;spi_tx_buf[7:0] = 8'ha0;spi_tx_buf_r[7:0] = 5'ha0;forever beginwait(I_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)) beginfirst_data_flag = 1;endwait(!I_spi_ss);while (!I_spi_ss) beginif (CPHA == 0 && CPOL == 0) begin@(negedge I_spi_clk)beginif (bit_cnt == 7) beginbit_cnt = 0;spi_tx_buf_r = spi_tx_buf_r + 1'b1;spi_tx_buf = spi_tx_buf_r;end else beginspi_tx_buf = {spi_tx_buf[6:0],1'b0};bit_cnt = bit_cnt + 1'b1;endendend if (CPHA == 0 && CPOL == 1) begin@(posedge I_spi_clk)beginif (bit_cnt == 7) beginbit_cnt = 0;spi_tx_buf_r = spi_tx_buf_r + 1'b1;spi_tx_buf = spi_tx_buf_r;end else beginspi_tx_buf = {spi_tx_buf[6:0],1'b0};bit_cnt = bit_cnt + 1'b1;endendendif (CPHA == 1 && CPOL == 0) begin@(posedge I_spi_clk)beginif (first_data_flag == 1'b1) beginfirst_data_flag = 0;endelse beginif (bit_cnt == 7) beginbit_cnt = 0;spi_tx_buf_r = spi_tx_buf_r + 1'b1;spi_tx_buf = spi_tx_buf_r;end else beginspi_tx_buf = {spi_tx_buf[6:0],1'b0};bit_cnt = bit_cnt + 1'b1;endendendend  if (CPHA == 1 && CPOL == 1) begin@(negedge I_spi_clk)beginif (first_data_flag == 1'b1) beginfirst_data_flag = 0;endelse beginif (bit_cnt == 7) beginbit_cnt = 0;spi_tx_buf_r = spi_tx_buf_r + 1'b1;spi_tx_buf = spi_tx_buf_r;end else beginspi_tx_buf = {spi_tx_buf[6:0],1'b0};bit_cnt = bit_cnt + 1'b1;endendendend  endend
endendmodule

三、仿真验证

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis配置篇 - 指定Redis配置的三种方式,以及Redis配置文件介绍

文章目录 1 指定Redis配置的三种方式1.1 通过命令行参数来指定Redis配置1.2 通过配置文件来指定Redis配置1.3 在服务器运行时更​​改 Redis 配置 2 关于Redis配置文件 1 指定Redis配置的三种方式 1.1 通过命令行参数来指定Redis配置 在redis启动时&#xff0c;可以直接通过命…

绿野仙踪不仅是童话,还是便宜又好用的产品测试法!

以 ChatGPT 为代表的大语言模型爆火后&#xff0c;推动了对话类人工智能产品的高速发展&#xff0c;我们已经看到了如智能助理、问答系统、自动写作等多种类型的个性化对话类 AI 服务。 AI 能力的提升让人们对智能 AI 产品的期望越来越高&#xff0c;相关产品的用户体验也因此变…

豆包MarsCode 合伙人计划限时招募中,推广最高赢万元现金!

豆包MarsCode 合伙人计划正式上线啦&#xff01;作为官方推出的推广激励项目&#xff0c;豆包MarsCode 编程助手号召和鼓励所有用户向我们推荐新用户。 现在正式开启首轮合伙人招募&#xff0c;诚邀各位有意愿推广普及 AI 编程产品的伙伴成为我们的合伙人&#xff0c;全国限量…

jmeter输出性能测试报告(常见问题处理与处理)

问题1&#xff1a;报错 WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows R 意思是&#xff1a;报没有权限 处理&#xff1a; 操作非gui生成测试报告的方法 cmd界面进入到 jmeter的bin目录 jmeter –n –t -l -e –o …

对后端返回的日期属性进行格式化(扩展 Spring MVC 的消息转换器)

格式化之前 格式化之后&#xff1a; 解决方式 方式一 在属性中加上注解&#xff0c;对日期进行格式化 JsonFormat(pattern "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;//JsonFormat(pattern &quo…

利用FnOS搭建虚拟云桌面,并搭建前端开发环境(一)

利用FnOS搭建虚拟云桌面&#xff0c;并搭建前端开发环境 一 飞牛FnOS官方文档一、安装FnOS【Win11系统】1.下载VirtualBox2.下载FnOS镜像3.创建虚拟机4.启动完成后&#xff0c;会进入这样一个界面&#xff0c;这个基本上后续就后台了 本人在网上冲浪了很久&#xff0c;一直也没…

DGX的优势

NVIDIA DGX 的 AI 领导力 文章目录 前言一、概述推动跨行业的 AI 创新二、优势客户体验到哪些好处?1. 利用生成式 AI 释放研究人员的潜力2. 加快现代应用程序的上市时间3. 利用 AI 改善客户体验三、性能性能很重要1. 为世界上最先进的超级计算机提供动力2. 打破世界纪录3. 提高…

ES6总结

1.let和const以及与var区别 1.1 作用域 var&#xff1a; 变量提升&#xff08;Hoisting&#xff09;&#xff1a;var 声明的变量会被提升到其作用域的顶部&#xff0c;但赋值不会提升。这意味着你可以在声明之前引用该变量&#xff08;但会得到 undefined&#xff09;。 con…

CSS元素显示类型

display 属性是 CSS 中最重要的属性之一&#xff0c;主要用来控制元素的布局&#xff0c;通过 display 属性您可以设置元素是否显示以及如何显示。 根据元素类型的不同&#xff0c;每个元素都有一个默认的 display 属性值&#xff0c;例如<div>默认的 display 属性值为 …

电脑端视频通过PCIE到FPGA端转UDP网络视频输出,基于XDMA+PHY芯片架构,提供3套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的PCIE方案我这里已有的以太网方案 3、PCIE基础知识扫描4、工程详细设计方案工程设计原理框图电脑端视频PCIE视频采集QT上位机XDMA配置及使用XDMA中断模块FDMA图像缓存UDP视频组包发送UDP协议栈MAC数据缓冲FIFO组Tri Mode E…

STM32编码器接口

一、概述 1、Encoder Interface 编码器接口概念 编码器接口可接收增量&#xff08;正交&#xff09;编码器的信号&#xff0c;根据编码器旋转产生的正交信号脉冲&#xff0c;自动控制CNT自增或自减&#xff0c;从而指示编码器的位置、旋转方向和旋转速度每个高级定时器和通用…

CSS 3D转换

在 CSS 中&#xff0c;除了可以对页面中的元素进行 2D 转换外&#xff0c;您也可以对象元素进行 3D转换&#xff08;将页面看作是一个三维空间来对页面中的元素进行移动、旋转、缩放和倾斜等操作&#xff09;。与 2D 转换相同&#xff0c;3D 转换同样不会影响周围的元素&#x…

CVE-2024-36971漏洞修复----Debian 10.13 内核升级

CVE-2024-36971漏洞修复---Debian 10.13 内核升级 1. 下载内核2. 安装依赖包3. 二进制安装3.1 上传3.2 解压3.3 修改配置文件3.4 编译3.5 安装内核及模块 4. 重启服务器并确认升级成功 1. 下载内核 到kernel.org下载新版的Kernel 由于开发那边不想让Kernel跨大版本,所以就升级…

OpenCV高级图形用户界面(1)创建滑动条函数createTrackbar()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 创建一个滑动条并将其附加到指定的窗口。 该函数 createTrackbar 创建一个具有指定名称和范围的滑动条&#xff08;滑块或范围控制&#xff09;…

机器学习笔记-1

文章目录 前言一、How to find a function二、Define Loss from Training Data三、Optimization总结 前言 机器学习&#xff08;Machine Learning, ML&#xff09;是一门让计算机通过数据来自动学习和改进的技术。它的核心理念是通过分析大量的历史数据来找到其中的规律&#…

Qt-链接数据库可视化操作

1. 概述 Qt 能够支持对常见数据库的操作&#xff0c;例如&#xff1a; MySQL、Oracle、SqlServer 等等。 Qt SQL模块中的API分为三层&#xff1a;驱动层、SQL接口层、用户接口层。 驱动层为数据库和SQL接口层之间提供了底层的桥梁。 SQL接口层提供了对数据库的访问&#xff0…

蓝桥杯【物联网】零基础到国奖之路:十六. 扩展模块之矩阵按键

蓝桥杯【物联网】零基础到国奖之路:十六. 扩展模块之矩阵按键 第一节 硬件解读第二节 CubeMX配置第三节 MDK代码 第一节 硬件解读 扩展模块和ADC模块是一摸一样的&#xff0c;插在主板上。 引脚对应关系&#xff1a; PB6-ROW1 PB7-ROW2 PB1-COLUMN1 PB0-COLUMN2 PA8-COLUMN3 …

adb安装教程(Windows10)

本章教程&#xff0c;主要介绍如何在Windows10操作系统上安装adb。 一、adb简介 ADB&#xff0c;全称为Android Debug Bridge&#xff0c;是Android开发中一个重要的命令行工具。它用于与Android设备进行通信&#xff0c;提供了多种功能来帮助开发者进行调试和应用管理。 二、下…

Qt第三课 ----------显示类的控件属性

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

美团Java一面

美团Java一面 9.24一面&#xff0c;已经寄了 收到的第一个面试&#xff0c;表现很不好 spring bean生命周期 作用域&#xff08;忘完了&#xff09; 为什么用redis缓存 redis和数据库的缓存一致性问题 redis集群下缓存更新不一致问题 aop说一下 arraylist和linkedlist 数据库的…