[米联客-安路飞龙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;elsespi_clk_r <= {spi_clk_r[2:0],I_spi_clk};
end//I_spi_ss去毛刺
always @(posedge I_clk or negedge I_rstn)beginif(I_rstn == 1'b0)spi_ss_r <= 4'd0;elsespi_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 @(*)beginif(CPHA)begin if(CPOL) spi_cap = spi_clkp;//CPHA=1  CPOL=1else  spi_cap = spi_clkn;   //CPHA=1  CPOL=0endelse begin if(CPOL) spi_cap = spi_clkn;//CPHA=0  CPOL=1else  spi_cap = spi_clkp;   //CPHA=0  CPOL=0    end
end

bits counter

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

移位模块:

//spi bit counter
always @(posedge I_clk)beginif(spi_rx_en&&spi_cap&&(spi_bit_cnt < BITS_LEN))       //计数到未到达参数BITS_LEN设定值spi_bit_cnt <= spi_bit_cnt + 1'b1;                    //spi_bit_cnt计数器+1else 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)beginif(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;elsespi_clk_r <= {spi_clk_r[2:0],I_spi_clk};
endalways @(posedge I_clk or negedge I_rstn)beginif(I_rstn == 1'b0)spi_ss_r <= 4'd0;elsespi_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 @(*)beginif(CPHA)begin if(CPOL) spi_cap = spi_clkp;//CPHA=1  CPOL=1 else  spi_cap = spi_clkn;   //CPHA=1  CPOL=0endelse 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)beginif(spi_rx_en&&spi_cap&&(spi_bit_cnt < BITS_LEN))       //计数到未到达参数BITS_LEN设定值spi_bit_cnt <= spi_bit_cnt + 1'b1;                    //spi_bit_cnt计数器+1else if(spi_rx_en==0||spi_bit_cnt == BITS_LEN)          //单次传输的长度由参数BITS_LEN来控制spi_bit_cnt <= 0;                                        //计数到达设定值,计数清零
end          //spi bit shift
always @(posedge I_clk)beginif(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 / 1psmodule 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 beginI_clk  = 1'b0;I_rstn = 1'b0;#100;I_rstn = 1'b1;
endalways #10   I_clk  = ~I_clk;   //时钟信号翻转,产生系统时钟initial begin#100;i = 0;forever beginI_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
endinitial 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 //每个时钟的下降沿更新需要发送的BITif(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;//重新跟新发送寄存器endelse begin spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器endendend//CPHA=0 COPL=1 默认SCLK为高电平,对于发送方,在对于第1个bit数据提前放到总线if(CPHA == 0 && CPOL ==1)begin @(posedge I_spi_clk)  begin //每个时钟的上升沿更新需要发送的BITif(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; //重新跟新发送寄存器endelse beginspi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器endendend//CPHA=1 COPL=0 默认SCLK为低电平,对于发送方,在第1个SCLK的跳变沿更新if(CPHA == 1 && CPOL ==0)begin @(posedge I_spi_clk)  beginif(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿first_data_flag = 0;//spi_tx_buf[7:0] = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据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;//重新跟新发送寄存器endelse begin spi_tx_buf = {spi_tx_buf[6:0],1'b0}; //数据移位,更新数据bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器endendendend//CPHA=1 COPL=1 默认SCLK为高电平,对于发送方,在第1个SCLK的跳变沿更新if(CPHA == 1 && CPOL ==1)begin @(negedge I_spi_clk)  beginif(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿first_data_flag = 0;//spi_tx_buf[7:0] = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据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;//重新跟新发送寄存器endelse begin spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器endendendendendend
endendmodule

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

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

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

相关文章

Michael.W基于Foundry精读Openzeppelin第64期——UUPSUpgradeable.sol

Michael.W基于Foundry精读Openzeppelin第64期——UUPSUpgradeable.sol 0. 版本0.1 UUPSUpgradeable.sol 1. 目标合约2. 代码精读2.1 modifier onlyProxy()2.2 modifier notDelegated()2.3 proxiableUUID()2.4 upgradeTo(address newImplementation) && _authorizeUpgra…

linux服务器登录mysql无异常,本地登录报1045 -Access denied for user

1、本地登录linux服务器报“1045 -Access denied for user &#xff08;用户访问被拒绝&#xff09;” 造成上面链接问题的原因是&#xff0c;用户权限不足&#xff0c;需要在linux服务器上执行如下2条命令即可 CREATE USER root127.0.0.1 IDENTIFIED BY root123; GRANT ALL P…

新美业和传统美业的区别在哪些方面?连锁美业SaaS收银系统源码

新美业和传统美业在很多方面存在着显著的区别。传统美业通常指的是传统的美容美发行业&#xff0c;而新美业则更多地与科技、数字化和创新相关。随着科技的不断发展和消费者需求的变化&#xff0c;新美业将继续引领美容行业的发展趋势。 以下是传统美业和新美业之间的一些区别…

用 AI 写歌词,让音乐表达与众不同

在音乐的广袤天地中&#xff0c;我们都渴望通过独特的表达来触动人心&#xff0c;展现自我。而如今&#xff0c;AI 技术的崛起为音乐创作带来了全新的突破&#xff0c;让我们能够以一种前所未有的方式赋予音乐独特的灵魂。 “妙笔生词智能写歌词软件&#xff08;veve522&#…

Docker缩小镜像体积与搭建LNMP架构

镜像加速地址 {"registry-mirrors": ["https://docker.m.daocloud.io","https://docker.1panel.live"] } daemon.json 配置文件里面 bip 配置项中可以配置docker 的网段 {"graph": "/data/docker", #数据目录&#xff0…

领航Linux UDP:构建高效网络新纪元

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 引言Udp和Tcp的异同相同点不同点总结 1.1、socket1.2、bind1.3、recvfrom1.4、sendto2.1、代码2.1、说明3.1、代码3.2、说明 引言 在前几篇博客中&#xff0c;我们学习了Linux网络编程中的一些概念。…

【数据结构(邓俊辉)学习笔记】高级搜索树02——B树

文章目录 1. 大数据1.1 640 KB1.2 越来越大的数据1.3 越来越小的内存1.4 一秒与一天1.5 分级I/O1.6 1B 1KB 2. 结构2.1 观察体验2.2 多路平衡2.3 还是I/O2.4 深度统一2.5 阶次含义2.6 紧凑表示2.7 BTNode2.8 BTree 3. 查找3.1 算法过程3.2 操作实例3.3 算法实现3.4 主次成本3.…

JAVASE——图书管理系统

JAVASE图书管理系统 主要业务有&#xff1a;管理员(增删改查)&#xff0c;会员&#xff08;借书还书查看记录&#xff09; 管理员主要有&#xff1a;查看图书&#xff0c;增加图书&#xff0c;修改图书&#xff0c;会员管理&#xff0c;删除图书&#xff0c; 会员主要有&#x…

昇思25天学习打卡营第22天|GAN图像生成

今天是参加昇思25天学习打卡营的第22天&#xff0c;今天打卡的课程是“GAN图像生成”&#xff0c;这里做一个简单的分享。 1.简介 今天来学习“GAN图像生成”&#xff0c;这是一个基础的生成式模型。 生成式对抗网络(Generative Adversarial Networks&#xff0c;GAN)是一种…

Bug:时间字段显示有问题

Bug&#xff1a;时间字段显示有问题 文章目录 Bug&#xff1a;时间字段显示有问题1、问题2、解决方法一&#xff1a;添加注解3、解决方法二&#xff1a;消息转换器自定义对象映射器配置消息转换器 1、问题 ​ 在后端传输时间给前端的时候&#xff0c;发现前端的时间显示有问题…

[Spring] Spring Web MVC案例实战

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

AV1技术学习:Translational Motion Compensation

编码块根据运动矢量在参考帧中找到相应的预测块&#xff0c;如下图所示&#xff0c;当前块的左上角的位置为(x0, y0)&#xff0c;在参考帧中找到同样位置(x0, y0)的块&#xff0c;根据运动矢量移动到目标参考块&#xff08;左上角位置为&#xff1a;(x1, y1)&#xff09;。 AV1…

前端a-tree遇到的问题

在使用a-tree时候&#xff0c;给虚拟滚动的高度&#xff0c;然后展开a-tree滑动一段距离 比如这样 随后你切换页面&#xff0c;在返回这个页面的时候 就会出现这样的bug 解决方法&#xff1a; onBeforeRouteLeave((to, from, next) > {// 可以在路由参数变化时执行的逻辑ke…

白山云荣获信通院“算网安全行业应用优秀案例”奖

日前&#xff0c;在由中国通信标准化协会算网融合产业及标准推进委员会与信通院共同组织召开的“2024年算网融合产业发展大会”上&#xff0c;白山云凭借创新的SD-WAN算网融合方案&#xff0c;荣获“算网安全行业应用优秀案例”奖。 算网融合是多元异构、海量泛在的算力设施&am…

path模块和HTTP协议

一。path模块常用API ./相对路径&#xff0c;/绝对路径 二&#xff0c;HTTP协议 1.请求报文 1.请求行 URL的组成 2.请求头 3.请求体 可以是空&#xff1a;GET请求 可以是字符串&#xff0c;还可以是json&#xff1a;POST请求 2.响应报文 1.响应行 HTTP / 1.1 200 OK H…

VsCode 与远程服务器 ssh免密登录

首先配置信息 加入下列信息 Host qb-zn HostName 8.1xxx.2xx.3xx User root ForwardAgent yes Port 22 IdentityFile ~/.ssh/id_rsa 找到自己的公钥&#xff0c;不带pub是私钥&#xff0c;打死都不能给别人。复制公钥 拿到公钥后&#xff0c;来到远程服务器 vim ~/.ss…

Leetcode—3011. 判断一个数组是否可以变为有序【中等】(__builtin_popcount()、ranges::is_sorted())

2024每日刷题&#xff08;144&#xff09; Leetcode—3011. 判断一个数组是否可以变为有序 O(n)复杂度实现代码 class Solution { public:bool canSortArray(vector<int>& nums) {// 二进制数位下1数目相同的元素就不进行组内排序// 只进行分组// 当前组的值若小于…

人工智能算法工程师(中级)课程12-PyTorch神经网络之LSTM和GRU网络与代码详解1

大家好,我是微学AI,今天给大家介绍一下人工智能算法工程师(中级)课程12-PyTorch神经网络之LSTM和GRU网络与代码详解。在深度学习领域,循环神经网络(RNN)因其处理序列数据的能力而备受关注。然而,传统的RNN存在梯度消失和梯度爆炸的问题,这使得它在长序列任务中的表现不尽…

MySQL--C_C++语言连接访问

Connector/C的使用 首先需要在mysql官网下载C接口库 解压指令 tar -zxvf 压缩包名 下载并解压好后 但是还有比这更优的做法。 这样子手动安装不仅麻烦&#xff0c;还可能存在兼容性的问题。 其实在我们使用yum安装mysql时&#xff0c;大概率会自动帮我们把其他的环境都安装…

【Datawhale AI夏令营】电力需求预测挑战赛 Task01

整个学习活动&#xff0c;将带你从 跑通最简的Baseline&#xff0c;到了解竞赛通用流程、深入各个竞赛环节&#xff0c;精读Baseline与进阶实践 文章目录 一、赛题背景二、赛题任务三、实践步骤学习规划分析思路常见时序场景 task01codecode 解读 一、赛题背景 随着全球经济的…