完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
1、什么是SPI协议
SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是 Motorola 公司提出的一种同步串行接口威廉希尔官方网站 ,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输,广泛用于 EEPROM、Flash、RTC(实时时钟)、ADC(数模转换 器)、DSP(数字信号处理器)以及数字信号解码器上,是常用的、重要的低速通讯协议之一。 SPI 通讯协议的优点是支持全双工通信,通讯方式较为简单,且相对数据传输速率较快;缺点是没有指定的流控制,没有应答机制,在数据可靠性上有一定缺陷。 2、SPI协议详述 2.1、SPI协议物理层 SPI 通讯设备的通讯模式是主从通讯模式,通讯双方有主从之分,根据从机设备的数量,SPI 通讯设备之间的连接方式可分为一主一从和一主多从。 SPI总线传输只需要4根线就能完成,这四根线的作用分别如下:
SPI总线传输一共有4种模式,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中CPOL参数规定了SCK时钟信号空闲状态的电平,CPHA规定了数据是在SCK时钟的上升沿被采样还是下降沿被采样。 SPI总线的极性--时钟极性 时钟极性决定SPI总线空闲时的时钟信号是高电平还是低电平。CPOL = 1:表示空闲时是高电平;CPOL = 0:表示空闲时是低电平。 SPI总线的相位--时钟相位 时钟相位决定SPI总线从哪个跳变沿开始采样数据。CPHA = 0:在时钟信号SCK的第1个跳变沿采样;CPHA = 1:在时钟信号SCK的第2个跳变沿采样。 这四种模式的时序图如下图所示:
2.3、SPI协议通信过程 下面以模式 0 为例,讲解一下 SPI 基本的通讯过程: SCK、MOSI、CS_N 信号均由主机控制产生, SCK 是时钟信号,用以同步数据,MOSI 是主机输出从机输入信号,主机通过此信号线传输数据给从机,CS_N 为片选信号,用以选定从机设备,低电平有效;而 MISO 的信号由 从机产生,主机通过该信号线读取从机的数据。MOSI 与 MISO 的信号只在 CS_N 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。 在图中的标号1处,CS_N 信号线由高变低,是 SPI 通讯的起始信号。CS_N 是每 个从机各自独占的信号线,当从机在自己的 CS_N 线检测到起始信号后,就知道自己被主 机选中了,开始准备与主机通讯。在图中的标号6处,CS_N 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。 SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。MOSI 及 MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB 先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定,一般都会采用MSB 先行模式。 MOSI 及 MISO 的数据在 SCK 的下降沿期间变化输出, 在 SCK 的上升沿时被采样。即在 SCK 的上升沿时刻,MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及 MISO 为下一次表示数据做准备。 SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。 2.4、SPI协议的特性
优势:
接下来实现的SPI驱动代码特性如下:MSB 先行;仅限模式0;每次传输8位(1个BYTE)。 3.1、接口定义与整体设计 SPI驱动的整体框图、输入输出信号如下所示: 其中信号描述如下: 该模块的使用方法如下:
Verilog代码并不复杂,结合下图的SPI通信过程,可以发现以下要点:
`timescale 1ns/1ns //时间单位/精度 // 模式0 module spi_drive ( // 系统接口 input sys_clk , // 全局时钟50MHz input sys_rst_n , // 复位信号,低电平有效 // 用户接口 input spi_start , // 发送传输开始信号,一个高电平 input spi_end , // 发送传输结束信号,一个高电平 input [7:0] data_send , // 要发送的数据 output reg [7:0] data_rec , // 接收到的数据 output reg send_done , // 主机发送一个字节完毕标志位 output reg rec_done , // 主机接收一个字节完毕标志位 // SPI物理接口 input spi_miso , // SPI串行输入,用来接收从机的数据 output reg spi_sclk , // SPI时钟 output reg spi_cs , // SPI片选信号,低电平有效 output reg spi_mosi // SPI输出,用来给从机发送数据 ); reg [1:0] cnt; //4分频计数器 reg [3:0] bit_cnt_send; //发送计数器 reg [3:0] bit_cnt_rec; //接收计数器 reg spi_end_req; //结束请求 //4分频计数器 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) cnt <= 2'd0; else if(!spi_cs)begin if(cnt == 2'd3) cnt <= 2'd0; else cnt <= cnt + 1'b1; end else cnt <= 2'd0; end // 生成spi_sclk时钟 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) spi_sclk <= 1'b0; //模式0默认为低电平 else if(!spi_cs)begin //在SPI传输过程中 if(cnt == 2'd0 ) spi_sclk <= 1'b0; else if (cnt == 2'd2) spi_sclk <= 1'b1; else spi_sclk <= spi_sclk; end else spi_sclk <= 1'b0; //模式0默认为低电平 end // 生成片选信号spi_cs always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) spi_cs <= 1'b1; //默认为高电平 else if(spi_start) //开始SPI准备传输,拉低片选信号 spi_cs <= 1'b0; //收到了SPI结束信号,且结束了最近的一个BYTE else if(spi_end_req && (cnt == 2'd1 && bit_cnt_rec == 4'd0)) spi_cs <= 1'b1; //拉高片选信号,结束SPI传输 end // 生成结束请求信号(捕捉spi_end信号) always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) spi_end_req <= 1'b0; //默认不使能 else if(spi_cs) spi_end_req <= 1'b0; //结束SPI传输后拉低请求 else if(spi_end) spi_end_req <= 1'b1; //接收到SPI结束信号后就把结束请求拉高 end // 发送数据过程-------------------------------------------------------------------- // 发送数据 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin spi_mosi <= 1'b0; //模式0空闲 bit_cnt_send <= 4'd0; end else if(cnt == 2'd0 && !spi_cs)begin //模式0的上升沿 spi_mosi <= data_send[7-bit_cnt_send]; //发送数据移位 if(bit_cnt_send == 4'd7) //发送完8bit bit_cnt_send <= 4'd0; else bit_cnt_send <= bit_cnt_send + 1'b1; end else if(spi_cs)begin //非传输时间段 spi_mosi <= 1'b0; //模式0空闲 bit_cnt_send <= 4'd0; end else begin spi_mosi <= spi_mosi; bit_cnt_send <= bit_cnt_send; end end // 发送数据标志 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) send_done <= 1'b0; else if(cnt == 2'd0 && bit_cnt_send == 4'd7) //发送完了8bit数据 send_done <= 1'b1; //拉高一个周期,表示发送完成 else send_done <= 1'b0; end // 接收数据过程-------------------------------------------------------------------- // 接收数据spi_miso always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin data_rec <= 8'd0; bit_cnt_rec <= 4'd0; end else if(cnt == 2'd2 && !spi_cs)begin //模式0的上升沿 data_rec[7-bit_cnt_rec] <= spi_miso; //移位接收 if(bit_cnt_rec == 4'd7) //接收完了8bit bit_cnt_rec <= 4'd0; else bit_cnt_rec <= bit_cnt_rec + 1'b1; end else if(spi_cs)begin bit_cnt_rec <= 4'd0; end else begin data_rec <= data_rec; bit_cnt_rec <= bit_cnt_rec; end end // 接收数据标志 always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) rec_done <= 1'b0; else if(cnt == 2'd2 && bit_cnt_rec == 4'd7) //接收完了8bit rec_done <= 1'b1; //拉高一个周期,表示接收完成 else rec_done <= 1'b0; end endmodule 4、Testbench及仿真结果 4.1、单个BYTE的仿真 使用该SPI驱动,向从机发送单个BYTE数据8‘b01010101,观察其仿真时序是否正确: //------------------------------------------------ //--SPI驱动仿真(模式0,1个BYTE) //------------------------------------------------ `timescale 1ns/1ns //时间单位/精度 //------------<模块及端口声明>---------------------------------------- module tb_spi_drive(); //系统接口 reg sys_clk ; // 全局时钟50MHz reg sys_rst_n ; // 复位信号,低电平有效 //用户接口 reg spi_start ; // 发送传输开始信号,一个高电平 reg spi_end ; // 发送传输结束信号,一个高电平 reg [7:0] data_send ; // 要发送的数据 wire [7:0] data_rec ; // 接收到的数据 wire send_done ; // 主机发送一个字节完毕标志位 wire rec_done ; // 主机接收一个字节完毕标志位 //SPI物理接口 reg spi_miso ; // SPI串行输入,用来接收从机的数据 wire spi_sclk ; // SPI时钟 wire spi_cs ; // SPI片选信号 wire spi_mosi ; // SPI输出,用来给从机发送数据 //仿真用 reg [3:0] cnt_send ; //发送数据计数器,0-15 //------------<例化SPI驱动模块(模式0)>---------------------------------------- spi_drive spi_drive_inst( .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n ), .spi_start (spi_start ), .spi_end (spi_end ), .data_send (data_send ), .data_rec (data_rec ), .send_done (send_done ), .rec_done (rec_done ), .spi_miso (spi_miso ), .spi_sclk (spi_sclk ), .spi_cs (spi_cs ), .spi_mosi (spi_mosi ) ); //------------<设置初始测试条件>---------------------------------------- initial begin sys_clk = 1'b0; //初始时钟为0 sys_rst_n <= 1'b0; //初始复位 spi_start <= 1'b0; data_send <= 8'd0; spi_miso <= 1'bz; spi_end <= 1'b0; #80 //80个时钟周期后 sys_rst_n <= 1'b1; //拉高复位,系统进入工作状态 #30 //30个时钟周期后拉高SPI开始信号,开始SPI传输 spi_start <= 1'b1;data_send <= 8'b01010101; #20 spi_start <= 1'b0; @(posedge send_done) //一个BYTE发送完成 spi_end <= 1'b1; #20 spi_end <= 1'b0; //拉高一个周期结束信号 end //------------<设置时钟>---------------------------------------------- always #10 sys_clk = ~sys_clk; //系统时钟周期20ns endmodule 仿真结果如下: 可以看到,在拉高了spi_start开始传输信号后,主机开始发送数据,MOSI上的数据分别是01010101,发送完一个BYTE的数据后,send_done拉高。此时拉高结束信号spi_end,就终结了这次SPI传输,完成了单个BYTE的SPI传输。 4.2、多个BYTE的仿真 使用该SPI驱动,依次向从机发送数据8‘d0~8‘d10,观察其仿真时序是否正确: //------------------------------------------------ //--SPI驱动仿真(模式0) //------------------------------------------------ `timescale 1ns/1ns //时间单位/精度 //------------<模块及端口声明>---------------------------------------- module tb_spi_drive(); //系统接口 reg sys_clk ; // 全局时钟50MHz reg sys_rst_n ; // 复位信号,低电平有效 //用户接口 reg spi_start ; // 发送传输开始信号,一个高电平 reg spi_end ; // 发送传输结束信号,一个高电平 reg [7:0] data_send ; // 要发送的数据 wire [7:0] data_rec ; // 接收到的数据 wire send_done ; // 主机发送一个字节完毕标志位 wire rec_done ; // 主机接收一个字节完毕标志位 //SPI物理接口 reg spi_miso ; // SPI串行输入,用来接收从机的数据 wire spi_sclk ; // SPI时钟 wire spi_cs ; // SPI片选信号 wire spi_mosi ; // SPI输出,用来给从机发送数据 //仿真用 reg [3:0] cnt_send ; //发送数据计数器,0-15 //------------<例化SPI驱动模块(模式0)>---------------------------------------- spi_drive spi_drive_inst( .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n ), .spi_start (spi_start ), .spi_end (spi_end ), .data_send (data_send ), .data_rec (data_rec ), .send_done (send_done ), .rec_done (rec_done ), .spi_miso (spi_miso ), .spi_sclk (spi_sclk ), .spi_cs (spi_cs ), .spi_mosi (spi_mosi ) ); //------------<设置初始测试条件>---------------------------------------- initial begin sys_clk = 1'b0; //初始时钟为0 sys_rst_n <= 1'b0; //初始复位 spi_start <= 1'b0; data_send <= 8'd0; spi_miso <= 1'bz; spi_end <= 1'b0; #80 //80个时钟周期后 sys_rst_n <= 1'b1; //拉高复位,系统进入工作状态 #30 //30个时钟周期后拉高SPI开始信号,开始SPI传输 spi_start <= 1'b1; #20 spi_start <= 1'b0; end always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin data_send <= 8'd0; spi_end <= 1'b0; cnt_send <= 4'd0; end else if(send_done)begin //数据发送完成 if(cnt_send == 4'd10)begin cnt_send <= 4'd0; spi_end <= 1'b1; //拉高结束标志,结束SPI传输过程 data_send <= 8'd0; end else begin cnt_send <= cnt_send + 4'd1; spi_end <= 1'b0; data_send <= data_send + 4'd1; //发送数据累加 end end else begin data_send <= data_send; spi_end <= 1'b0; //其他时候保持SPI传输(不结束) end end //------------<设置时钟>---------------------------------------------- always #10 sys_clk = ~sys_clk; //系统时钟周期20ns endmodule 仿真结果如下: 可以看到,在拉高了spi_start开始传输信号后,主机一直在发送数据,MOSI上的数据分别是8‘d0~8‘d10,每次发送一个BYTE的数据后,send_done即拉高一次。当结束信号spi_end被拉高后,就终结了这次SPI传输。 5、其他
|
|
|
|
只有小组成员才能发言,加入小组>>
imx6ull 和 lan8742 工作起来不正常, ping 老是丢包
2433 浏览 0 评论
3341 浏览 9 评论
3021 浏览 16 评论
3514 浏览 1 评论
9118 浏览 16 评论
1242浏览 3评论
635浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
627浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2373浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1936浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-1-23 07:59 , Processed in 1.184245 second(s), Total 79, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号