一. SPI总线协议

        SPI(Serial Peripheral Interface)接口,中文为串行外设接口。它只需要3根线或4根线即可完成通信工作(这里讨论4根线的情况)。
        这4根通信线分别为NCS/NSS(片选信号)、SCK/SCLK(串行同步时钟)、MOSI/SDO(主机输出从机输入,Master Output Slave Input)、MISO/SDI(主机输入从机输出)。
        SPI通信有四种方式,由CPOL(时钟极性)、CPHA(时钟相位)的4种组合决定的。CPOL决定总线空闲时,SCK是高电平还是低电平(CPOL=,0,无数据传输时,SCK=0;CPOL=1,无数据传输时,SCK=1)。CPHA决定在数据开始传输时,SCK第几个跳变沿采集数据(CPHA=0,开始传输时,在第一个跳变沿采集数据,第二个跳变沿改变发送数据(即改变MISO或者MOSI线上电平);CPHA=1,开始传输是,在第一个跳变沿改变发送的数据,在第二个跳变沿采集数据)(见图1)。

图1
        确立可靠通信前,必须保证主从机处于同一种的传输方式,这里为方便起见,专门以CPOL=0,CPHA=0的传输方式进行讨论。需要注意的是:在CPOL=0,CPHA=0的情况下,主从机都在SCK上跳沿对数据进行采集,SCK下跳沿改变总线电平(见图2)。


图2
这里在使用FPGA实现SPI模块时,做一个规定:
1. 使用CPOL=0,、CPHA=0的传输方式;
2. 传输时,以最高位先输出,最后输出最低位;
3. FPGA实现的SPI模块作从机,SCK由外部主机提供;
4. 通信数据长度为8位。

二. FPGA的SPI从机实现

        实现SPI从机,可以分为两个模块:一个是SPI接收模块,另一个则是SPI发送模块。

1. 首先确定模块的输出输入管脚

        由标题一可以知道,SPI通信脚有4根线,我们还是用到时钟总线和模块复位脚,因此模块管脚可以定义为
module myspi(nrst, clk, ncs, mosi, miso, sck);
input clk, nrst;
input ncs, mosi, sck;
output miso;

2. SCK跳变沿检测

        原理十分简单:使用寄存器记录SCK状态,由状态判断SCK是否出现跳变沿。
reg[2:0] sck_edge;
always @ (posedge clk or negedge nrst)
begin
if(~nrst)
begin
sck_edge <= 3'b000;
end
else
begin
sck_edge <= {sck_edge[1:0], sck};
end
end
wire sck_riseedge, sck_falledge;
assign sck_riseedge = (sck_edge[2:1] == 2'b01); //检测到SCK由0变成1,则认为发现上跳沿
assign sck_falledge = (sck_edge[2:1] == 2'b10); //检测到SCK由1变成0,则认为发现下跳沿

3. SPI接收部分

SPI接收部分使用有限状态机:

状态1:等待SCK上跳沿,并将MOSI的数据移入移位寄存器byte_received,接收位数寄存器bit_received_cnt记录接收到的数据位数,接收到8位数据后转入状态2;
状态2:保存移位寄存器byte_received数据到接收缓存器rec_data,接收标志位/接收缓存器非空标志位rec_flag置高4个clk时钟周期后转入状态3;
状态3:清除rec_flag并转入状态1。
reg[7:0] byte_received;
reg[3:0] bit_received_cnt;
reg rec_flag;
reg[1:0] rec_status; //SPI接收部分状态机
reg[7:0] rec_data;
reg[2:0] rec_flag_width; //SPI接收完成标志位脉冲宽度寄存器
always @ (posedge clk or negedge nrst) //每次sck都会接收数据,spi的顶端模块状态机决定是否取用
begin
if(~nrst)
begin
byte_received <= 8'h00;
bit_received_cnt <= 4'h0;
rec_flag <= 1'b0;
rec_status <= 2'b00;
rec_flag_width <= 3'b000;
end
else
begin
if(~ncs)
begin
case (rec_status)
2'b00: begin
if(sck_riseedge)
begin
byte_received <= {byte_received[6:0], mosi};
if(bit_received_cnt == 4'h7)
begin
bit_received_cnt <= 4'b0000;
rec_status <= 2'b01;
end
else
begin
bit_received_cnt <= bit_received_cnt+1;
end
end
end
2'b01: begin
rec_data <= byte_received;
rec_flag <= 1'b1;
if(rec_flag_width==3'b100) begin
rec_flag_width <= 3'b000;
rec_status <= 2'b11;
end
else begin
rec_flag_width <= rec_flag_width+1;
end
end
2'b11: begin
rec_flag <= 1'b0;
rec_status <= 2'b00;
end
endcase
end
end
end

这里,使用rec_flag的原因是通知另一个模块处理接收数据(后面将会提到),rec_data若在下一次数据传输完成前不做处理则会丢失。

4. SPI发送部分

        SPI从机一般在解析主机发送的命令后,主动发出主机所需数据,所以,SPI发送部分,需要其他模块的触发,并将数据送往MISO管脚。
   SPI发送部分也离不开状态机:
状态1:等待发送触发标志位send_flag置高,一旦标志位send_flag置高,发送移位寄存器byte_sended存储外部触发模块的数据send_data,miso管脚输出发送数据最高位send_data[7],置位正在发送标志位sending_flag,转入状态2;
状态2:等待SCK上跳沿,即等待主机接收数据最高位后进入状态3;(其实这个状态可有可无的状态)
状态3:在SCK下跳沿,将发送移位寄存器byte_sended最高位移入miso管脚,当发送移位寄存器被移空,清除正在发送标志位sending_flag,进入状态4;
状态4:置低miso管脚,转入状态1。
reg miso;
reg sending_flag; //正在发送标志位
reg[7:0] byte_sended; //发送移位寄存器
reg[3:0] bit_sended_cnt; //SPI发送位计数器
reg[1:0] send_status; //SPI发送部分状态机
always @ (posedge clk or negedge nrst)
begin
if(~nrst)
begin
byte_sended <= 8'h00;
bit_sended_cnt <= 4'b0000;
send_status <= 2'b00;
sending_flag <= 1'b0;
end
else
begin
if(~ncs)
begin
case (send_status)
2'b00: begin
if(send_flag)
begin //锁存发送数据
send_status <= 2'b01; //2'b01;
byte_sended <= send_data;
sending_flag <= 1'b1;
miso <= send_data[7];
end
end
2'b01: begin //发送数据移入移位寄存器
if(sck_riseedge) begin
//miso <= byte_sended[7];
//byte_sended <= {byte_sended[6:0], 1'b0};
send_status <= 2'b11;
end
end
2'b11: begin //根据sck下降沿改变数据
miso <= byte_sended[7];
if(sck_falledge) ///---------------------------------------这里多移了一位
begin
//miso <= byte_sended[7];
byte_sended <= {byte_sended[6:0], 1'b0};
if(bit_sended_cnt == 4'b0111)
begin
send_status <= 2'b10;
bit_sended_cnt <= 4'b0000;
sending_flag <= 1'b0;
end
else
begin
bit_sended_cnt <= bit_sended_cnt+1;
end
end
end
2'b10: begin //数据发送完毕
send_status <= 2'b00;
//sending_flag <= 1'b0;
miso <= 1'b0;
end
endcase
end
end
end

经过实测,SCK频率低于clk频率8倍以上,通信可靠稳定,测试芯片为XC3S50-TQ144,平台为ISE,clk为25MHz。


FPGA的SPI从机模块实现的更多相关文章

  1. 如何让FPGA中的SPI与其他模块互动起来

    在上一篇文章<FPGA的SPI从机模块实现>中,已经实现了SPI的从机模块,如何通过SPI总线与FPGA内部其他模块进行通信,是本文的主要讨论内容. 一. 新建FPGA内部DAC控制模块 ...

  2. FPGA构造spi时序——AD7176为例(转)

    reference:https://blog.csdn.net/fzhykx/article/details/79490330 项目中用到了一种常见的低速接口(spi),于是整理了一下关于spi相关的 ...

  3. IIS7注册本机模块

    问题描述:打开mp4文件要映射给mod_h264_streaming.dll(http://h264.code-shop.com/trac/wiki/Mod-H264-Streaming-Intern ...

  4. python开发学习-day09(队列、多路IO阻塞、堡垒机模块、mysql操作模块)

    s12-20160312-day09 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: ...

  5. 基于FPGA的SPI FLASH控制器设计

    1.SPI FLASH的基本特征 本文实现用FPGA来设计SPI FLASH,FLASH型号为W25Q128BV.支持3种通信方式,SPI.Dual SPI和Quad SPI.FLASH的存储单元无法 ...

  6. ARM与FPGA通过spi通信设计2.spi master的实现

    这里主要放两个代码第一个是正常的不使用状态机的SPI主机代码:第二个是状态机SPI代码 1.不使用状态机:特权同学<深入浅出玩转FPGA>中DIY数码相框部分代码: /////////// ...

  7. ARM与FPGA通过spi通信设计1.spi基础知识

    SPI(Serial Peripheral Interface--串行外设接口)总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息.SPI总线可直接与各个厂家生产 ...

  8. 嵌入式驱动开发之dsp fpga通信接口---spi串行外围接口、emif sram接口

    -----------------------------------------author:pkf ------------------------------------------------ ...

  9. FPGA之SPI SD卡读操作

    这几天在FPGA调试与SD通信,读SD卡里的图片,之前接触32时没有去研究过SD卡,不太熟悉操作流程,在网上找了很多资料,也看了几个32开发板的资料,但大多数都讲得不是特别清楚,只能瞎操作了一番,在别 ...

随机推荐

  1. ysql+heartbeat+DRBD+LVS实现mysql高可用

    在企业应用中,mysql+heartbeat+DRBD+LVS是一套成熟的集群解决方案,通过heart+DRBD实现mysql的主 节点写操作的高可用性,而通过mysql+LVS实现数据库的主从复制和 ...

  2. pktgen使用详细教程

    网上有很多讲解pktgen的文章,但总是不够全面细致,看完之后自己还是不会写pktgen测试脚本,为此本文对pktgen进行详细的阐述,让大家看完本文后能够自己动手写pktgen shell. 1.p ...

  3. javax.Swing 使用GridBagLayout的程序栗子

    摘自https://zhidao.baidu.com/question/110748776.html javax.Swing 使用GridBagLayout的程序栗子 总共两个文件,第一个是启动文件, ...

  4. 序列变换(Lis变形)

    序列变换 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

  5. GTID复制报错处理:Last_Error: Error 'Can't drop database 'test'; database doesn't exist' on query

    创建GTID主从连接: mysql, master_user; 报错显示: Slave_IO_Running: Yes Slave_SQL_Running: No Last_Error: Error ...

  6. squid服务搭建

    Squid配置1. 什么是squidsquid可以做代理也可以做缓存squid缓存不仅可以节省宝贵的带宽资源,也可以大大降低服务器的I/O.  squid不仅可以做正向代理,又可以做反向代理. 正向代 ...

  7. 影响布局的inline-block的空白符的问题

    昨天切页面时,进行布局时,想改变以下方法换换口味,所以就抛弃了float方法,采用了display:inline-block方法,没想到却随之而来的带来了一个想不通的问题,那就是空白.废话不多说,上代 ...

  8. dojo.create\dojo.place\dojo.empty\dojo.destroy\dojo.body

    1.dojo.create 1.create a node; 2.set attributes on it;  3.place it in the DOM. dojo.create(/*String| ...

  9. 简单的javascript实例二(随页面滚动广告效果)

    方便以后copy 页面代码: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "htt ...

  10. iOS 使用自定义的字体

    一.新建一个工程,准备好要使用的字体,后缀为.ttf或者.otf格式. 二.将字体直接拖入工程项目中. 三.在Info.plist中添加一个新的Key:Fonts provided by applic ...