这里主要放两个代码第一个是正常的不使用状态机的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. DOS命令(一)

    1. echo 输出内容,用来输出文字. [例如:echo hello] 2. titile 标题,用来修改标题. 3. color 背景色前景色,用来设置背景色和前景色 0 = 黑色 8 = 灰色 ...

  2. 《C#与.NET程序员面试宝典》学习札记

    第2章 .NET概述 2.1-6~ .Net Framework / CLR / IL / Assembly IL:中间语言代码,不同语言(如C#,VB)的基于CLR的编译器编译生成的中间语言字节码, ...

  3. FCC(ES6写法) Map the Debris

    返回一个数组,其内容是把原数组中对应元素的平均海拔转换成其对应的轨道周期. 原数组中会包含格式化的对象内容,像这样 {name: 'name', avgAlt: avgAlt}. 思路: 直接使用公式 ...

  4. HTML5 history.pushState()和history.replaceState()新增、修改历史记录用法介绍

    抽空研究了下这两个新方法,确实可以解决很多问题 1.使用pushState()方法 可以控制浏览器自带的返回按钮: 有时候我们想让用户点击浏览器返回按钮时,不返回,或执行其他操作,这时,就用到hist ...

  5. [SQL]LeetCode175. 组合两个表 | Combine Two Tables

    Table: Person +-------------+---------+ | Column Name | Type | +-------------+---------+ | PersonId ...

  6. [Swift]LeetCode530. 二叉搜索树的最小绝对差 | Minimum Absolute Difference in BST

    Given a binary search tree with non-negative values, find the minimum absolute difference between va ...

  7. [Swift]LeetCode740. 删除与获得点数 | Delete and Earn

    Given an array nums of integers, you can perform operations on the array. In each operation, you pic ...

  8. iOS学习——页面的传值方式

    一.简述 在iOS开发过程中,页面跳转时在页面之间进行数据传递是很常见的事情,我们称这个过程为页面传值.页面跳转过程中,从主页面跳转到子页面的数据传递称之为正向传值:反之,从子页面返回主页面时的数据传 ...

  9. 还在使用SimpleDateFormat?你的项目崩没?

    如果是在多线程环境下,就需要注意了. 要点: 1.加Synchronized同步: 2.使用ThreadLocal: 3.jdk8使用DateTimeFormatter替代SimpleDateForm ...

  10. Redis Windows下安装方法

    一.安装 首先在网上下载Redis,下载地址:https://github.com/MicrosoftArchive/redis/releases 根据电脑系统的实际情况选择32位还是64位,在这里我 ...