这里主要放两个代码第一个是正常的不使用状态机的SPI主机代码;第二个是状态机SPI代码

1.不使用状态机:特权同学《深入浅出玩转FPGA》中DIY数码相框部分代码:

////////////////////////////////////////////////////////////////////////////////
module spi_ctrl(
clk,rst_n,
spi_miso,spi_mosi,spi_clk,
spi_tx_en,spi_tx_rdy,spi_rx_en,spi_rx_rdy,spi_tx_db,spi_rx_db
); input clk; //FPAG输入时钟信号25MHz
input rst_n; //FPGA输入复位信号 input spi_miso; //SPI主机输入从机输出数据信号
output spi_mosi; //SPI主机输出从机输入数据信号
output spi_clk; //SPI时钟信号,由主机产生 input spi_tx_en; //SPI数据发送使能信号,高有效
output spi_tx_rdy; //SPI数据发送完成标志位,高有效
input spi_rx_en; //SPI数据接收使能信号,高有效
output spi_rx_rdy; //SPI数据接收完成标志位,高有效
input[:] spi_tx_db; //SPI数据发送寄存器
output[:] spi_rx_db; //SPI数据接收寄存器 //模拟SPI的时序模式为CPOL=1, CPHA=1,模拟速率为25Mbit //-------------------------------------------------
//SPI时序控制计数器,所有SPI时序由该计数器值控制
reg[:] cnt8; //SPI时序控制计数器,计数范围在0-18 always @(posedge clk or negedge rst_n)
if(!rst_n) cnt8 <= 'd0;
else if(spi_tx_en || spi_rx_en) begin
if(cnt8 < 'd18)cnt8 <= cnt8+1'b1; //SPI工作使能
else ; //计数到18停止,等待撤销spi使能
end
else cnt8 <= 'd0; //SPI关闭,计数停止 //-------------------------------------------------
//SPI时钟信号产生
reg spi_clkr; //SPI时钟信号,由主机产生 always @(posedge clk or negedge rst_n)
if(!rst_n) spi_clkr <= 'b1;
else if(cnt8 > 'd1 && cnt8 < 5'd18) spi_clkr <= ~spi_clkr; //在cnt8处于2-17时SPI时钟有效翻转 assign spi_clk = spi_clkr; //-------------------------------------------------
//SPI主机输出数据控制
reg spi_mosir; //SPI主机输出从机输入数据信号 always @(posedge clk or negedge rst_n)
if(!rst_n) spi_mosir <= 'b1;
else if(spi_tx_en) begin
case(cnt8[:]) //主机发送8bit数据
'd1: spi_mosir <= spi_tx_db[7]; //发送bit7
'd2: spi_mosir <= spi_tx_db[6]; //发送bit6
'd3: spi_mosir <= spi_tx_db[5]; //发送bit5
'd4: spi_mosir <= spi_tx_db[4]; //发送bit4
'd5: spi_mosir <= spi_tx_db[3]; //发送bit3
'd6: spi_mosir <= spi_tx_db[2]; //发送bit2
'd7: spi_mosir <= spi_tx_db[1]; //发送bit1
'd8: spi_mosir <= spi_tx_db[0]; //发送bit0
default: spi_mosir <= 'b1; //spi_mosi没有输出时应保持高电平
endcase
end
else spi_mosir <= 'b1; //spi_mosi没有输出时应保持高电平 assign spi_mosi = spi_mosir; //-------------------------------------------------
//SPI主机输入数据控制
reg[:] spi_rx_dbr; //SPI主机输入从机输出数据总线寄存器 always @(posedge clk or negedge rst_n)
if(!rst_n) spi_rx_dbr <= 'hff;
else if(spi_rx_en) begin
case(cnt8) //主机接收并锁存8bit数据
'd3: spi_rx_dbr[7] <= spi_miso; //接收bit7
'd5: spi_rx_dbr[6] <= spi_miso; //接收bit6
'd7: spi_rx_dbr[5] <= spi_miso; //接收bit5
'd9: spi_rx_dbr[4] <= spi_miso; //接收bit4
'd11: spi_rx_dbr[3] <= spi_miso; //接收bit3
'd13: spi_rx_dbr[2] <= spi_miso; //接收bit2
'd15: spi_rx_dbr[1] <= spi_miso; //接收bit1
'd17: spi_rx_dbr[0] <= spi_miso; //接收bit0
default: ;
endcase
end assign spi_rx_db = spi_rx_dbr; //-------------------------------------------------
//SPI数据发送完成标志位,高有效
assign spi_tx_rdy = (cnt8 == 'd18)/* & spi_tx_en)*/; //-------------------------------------------------
//SPI数据接收完成标志位,高有效
assign spi_rx_rdy = (cnt8 == 'd18)/* & spi_rx_en)*/; endmodule

2.使用状态机的SPI master(来源网络)

module spi_master
(
input sys_clk,
input rst,
output nCS, //chip select (SPI mode)
output DCLK, //spi clock
output MOSI, //spi master data output
input MISO, //spi master input
input CPOL,
input CPHA,
input nCS_ctrl,
input[:] clk_div,
input wr_req,
output wr_ack,
input[:] data_in,
output[:] data_out
);
//状态机状态
localparam IDLE = ;
localparam DCLK_EDGE = ;
localparam DCLK_IDLE = ;
localparam ACK = ;
localparam LAST_HALF_CYCLE = ;
localparam ACK_WAIT = ; reg DCLK_reg;
reg[:] MOSI_shift;//移位寄存器
reg[:] MISO_shift;
reg[:] state;
reg[:] next_state;
reg [:] clk_cnt;
reg[:] clk_edge_cnt;
assign MOSI = MOSI_shift[];
assign DCLK = DCLK_reg;
assign data_out = MISO_shift;
assign wr_ack = (state == ACK);
assign nCS = nCS_ctrl; /*************这个就是状态机的定义**************/
always@(posedge sys_clk or posedge rst)
begin
if(rst)
state <= IDLE;
else
state <= next_state;
end
/****************end*************************/ /****************状态机的具体过程*************/
always@(*)
begin
case(state)
IDLE:
if(wr_req == 'b1)
next_state <= DCLK_IDLE;
else
next_state <= IDLE;
DCLK_IDLE:
//half a SPI clock cycle produces a clock edge//半个SPI时钟周期产生时钟边沿
if(clk_cnt == clk_div)
next_state <= DCLK_EDGE;
else
next_state <= DCLK_IDLE;
DCLK_EDGE:
//a SPI byte with a total of 16 clock edges//一个SPI字节,总共有16个时钟边沿
if(clk_edge_cnt == 'd15)
next_state <= LAST_HALF_CYCLE;
else
next_state <= DCLK_IDLE;
//this is the last data edge //这是最后一个数据边缘
LAST_HALF_CYCLE:
if(clk_cnt == clk_div)
next_state <= ACK;
else
next_state <= LAST_HALF_CYCLE;
//send one byte complete//发送一个字节完成
ACK:
next_state <= ACK_WAIT;
//wait for one clock cycle, to ensure that the cancel request signal//等待一个时钟周期,以确保取消请求信号
ACK_WAIT:
next_state <= IDLE;
default:
next_state <= IDLE;
endcase
end /****************时钟翻转************************/
always@(posedge sys_clk or posedge rst)
begin
if(rst) /*在空闲状态之前,SCK一直保持CPOL的极性*/
DCLK_reg <= 'b0;
else if(state == IDLE)
DCLK_reg <= CPOL;
else if(state == DCLK_EDGE) /*边缘检测时,反转SCK*/
DCLK_reg <= ~DCLK_reg;//SPI clock edge
end
/****************end*****************************/ //SPI clock wait counter /*一个计数器*/
always@(posedge sys_clk or posedge rst)
begin
if(rst)
clk_cnt <= 'd0;
else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE)
clk_cnt <= clk_cnt + 'd1;
else
clk_cnt <= 'd0;
end
//SPI clock edge counter
always@(posedge sys_clk or posedge rst)
begin
if(rst)
clk_edge_cnt <= 'd0;
else if(state == DCLK_EDGE)
clk_edge_cnt <= clk_edge_cnt + 'd1;
else if(state == IDLE)
clk_edge_cnt <= 'd0;
end //SPI data output /*这里就是SPI输出的移位方式*/
always@(posedge sys_clk or posedge rst)
begin
if(rst)
MOSI_shift <= 'd0;
else if(state == IDLE && wr_req)
MOSI_shift <= data_in;
else if(state == DCLK_EDGE)
if(CPHA == 'b0 && clk_edge_cnt[0] == 1'b1) /*两种方式,取决于CPHA*/
MOSI_shift <= {MOSI_shift[:],MOSI_shift[]}; /*常见的移位语句,大家要敏感*/
else if(CPHA == 'b1 && (clk_edge_cnt != 5'd0 && clk_edge_cnt[] == 'b0))
MOSI_shift <= {MOSI_shift[:],MOSI_shift[]};
end
//SPI data input
always@(posedge sys_clk or posedge rst)
begin
if(rst)
MISO_shift <= 'd0;
else if(state == IDLE && wr_req)
MISO_shift <= 'h00;
else if(state == DCLK_EDGE)
if(CPHA == 'b0 && clk_edge_cnt[0] == 1'b0)
MISO_shift <= {MISO_shift[:],MISO}; /*MISO输入,然后进行移位*/
else if(CPHA == 'b1 && (clk_edge_cnt[0] == 1'b1))
MISO_shift <= {MISO_shift[:],MISO};
end
endmodule

第二个例子实现了较为全面的spi主机功能,可以设置SPI相位和极性,有较高的参考价值

以上两个源代码可供大家参考

ARM与FPGA通过spi通信设计2.spi master的实现的更多相关文章

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

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

  2. SPI通信实验---verilog(FPGA作为从机,使用可读可写)

    本实验讲究实用性,故设计思想为:主机先向从机发送地址,若是向从机写入数据,则向从机发送数据,若是读取从机数据,则向从机发送时钟,然后在时钟下降沿读取数据即可.cs信号上升沿作为SPI通信的结束信号.r ...

  3. 【6集iCore3_ADP触摸屏驱动讲解视频】6-2 基于FSMC总线的ARM与FPGA通信

    视频简介: 该视频介绍基于FSMC总线的ARM与FPGA通信   源视频包下载地址: 链接:http://pan.baidu.com/s/1slJDoQD 密码:tmw7   银杏科技优酷视频发布区: ...

  4. 012 基于FPGA的网口通信实例设计【转载】

    一.网口通信设计分类 通过上面其他章节的介绍,网口千兆通信,可以使用TCP或者UDP协议,可以外挂PHY片或者不挂PHY片,总结下来就有下面几种方式完成通信: 图8‑17基于FPGA的网口通信实例设计 ...

  5. FPGA+ARM or FPGA+DSP?

    网上有人说.现在的FPGA,ARM功能已经强大到无需DSP协助处理了,未来DSP会不会消声灭迹?是DSP取代FPGA和ARM,还是ARM,FPGA取代DSP呢?担心好不容易学精了DSP,结果DSP变成 ...

  6. 干货分享,FPGA硬件系统的设计技巧

    PGA的硬件设计不同于DSP和ARM系统,比较灵活和自由.只要设计好专用管脚的电路,通用I/O的连接可以自己定义.因此,FPGA的电路设计中会有一些特殊的技巧可以参考. 1. FPGA管脚兼容性设计 ...

  7. OLED的波形曲线、进度条、图片显示(STM32 HAL库 模拟SPI通信 5线OLED屏幕)详细篇

    少废话,先上效果图 屏幕显示效果         全家福 一.基础认识及引脚介绍 屏幕参数: 尺寸:0.96英寸 分辨率:128*64 驱动芯片:SSD1306 驱动接口协议:SPI 引脚说明: 二. ...

  8. [转]什么是SPI通信

    SPI:高速同步串行口.3-4线接口,收发独立.可同步进行. SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口.是Motorola首先在其MC6 ...

  9. 理解一下单片机的I2C和SPI通信

    应某位网友要求,今天说一下单片机的I2C SPI通信,可能说不清楚,因为这毕竟要做实验才可完全理解. I2C和SPI是两种不同的通信协议. 听到协议,似乎高不可攀,其实协议就是人们定义的一个标准而已, ...

随机推荐

  1. js-day04--Ajax应用--二级联动

    Ajax概述和实用需求 Ajax介绍/阿贾克斯:一.Ajax不是一项具体的技术,而是几门技术的综合应用. Javascript.XHTML和CSS.DOM.XML和XMLHttpRequest.二.A ...

  2. 在 Android 的文字编辑控件 (TEdit) 中, 如何按下 Enter 就隐藏虚拟键盘

    在 Windows 的应用中,我们常常为了让使用者能够快速输入,在Edit元件中的onKeyUp或者 onKeyDown 事件中主动侦测使用者输入的字元是否有换行符号 (Enter),当使用者按下了E ...

  3. Java作业十二(2017-11-13)

    /*继承与抽象类*/ package com.baidu.www; abstract class Person { private String name; private int age; publ ...

  4. [Swift]LeetCode214. 最短回文串 | Shortest Palindrome

    Given a string s, you are allowed to convert it to a palindrome by adding characters in front of it. ...

  5. [Swift]LeetCode420. 强密码检验器 | Strong Password Checker

    A password is considered strong if below conditions are all met: It has at least 6 characters and at ...

  6. [Swift]LeetCode826. 安排工作以达到最大收益 | Most Profit Assigning Work

    We have jobs: difficulty[i] is the difficulty of the ith job, and profit[i] is the profit of the ith ...

  7. [Swift]LeetCode904. 水果成篮 | Fruit Into Baskets

    In a row of trees, the i-th tree produces fruit with type tree[i]. You start at any tree of your cho ...

  8. Python档案袋( 面向对象 )

    类即是一个模型,根据模型建立起不同的对象,对象间拥有共同的一些属性 简单的类: class P: #类变量,所有实例共享变量,推荐使用方法是:类名.类变量名 pvarx="ppvar1&qu ...

  9. 【jpa】spring data jpa 配置使用

    1.spring data jpa 简单介绍 jpa是用于对象持久化的API,jpa是一种规范,而其他的ORM框架(hibernate,topLink等)是其实现,所以jpa可以使用不同的实现方式,修 ...

  10. TDD in .NET Core - 简介

    本文很多内容来自选自TDD实例一书. 预备知识 最好有一些预备知识,例如xUnit,Moq,如何编写易于测试的代码,这些内容我都写了文章:https://www.cnblogs.com/cgzl/p/ ...