对SPI协议的理解

  • spi协议
  • verilog

最近刚做了一个项目,现在还没做完,但是我发现官方的ip写得很全面,但是很复杂,许多东西对于速度要求不是很高的就用不着,比如一个crc,crc本身计算并不复杂,软件的速度一般都可以计算,何况是一个数据只有8bits的spi,所以我决定做一个简单的spi。
        spi协议本身并不难,理解起来也很容易。spi主要就是由主机发送指令对从机进行操作,一般所有操作都是由主机发起的,现在我会以我的理解用verilog对主机进行描述,不规范和有错误的地方欢迎指正。

spi协议

你以为我就直接将spi协议呢? 嘿嘿,百度吧,一搜一大堆,我就不赘诉了。

总的来说就是四根线,mosi,miso,sck和cs,三线模式下为半双工通信(同一时刻只能输入或者输出),四线模式下为全双工通信(同一时刻既能输入,又能输出),连接方式如下图所示:

   这是一个一主多从的连接方式,每个设备都会收到时钟和数据,但是决定哪一个从机去响应就由片选信号(cs)决定;
sck : 主机时钟输出
mosi:主机输出,从机输入,与从机的mosi或者单向的sdi连接;
miso:主机输入,从机输出,与从机的miso或者单向的sdo连接;
cs :chip select (片选信号)

spi一共有四种模式:mode0, mode1,mode2, mode3,一般由CPOL,CPHA 控制它的初始时钟电平,相位;需要注意的是mode0和mode2是第一个时钟沿是采样沿,数据在第一个时钟沿到来之前需要准备好mosi的数据。

1.CPOL = 0,CPHA = 0(mode0):此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿,时序如下:

2.CPOL = 0,CPHA = 1(mode1):此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿,时序如下:

ps:粘贴一半发现原版文档值写了前两种模式,崩溃,重新找图

3.CPOL = 1,CPHA = 0(mode2):此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿,时序如下:

4.CPOL = 1,CPHA = 1(mode3):此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿,时序如下:

ps:不好意思,盗图有点东拼西凑,实在是懒得画图

关于三线和四线模式,我看到官方文档里面是mosi和miso都是作为一个双向io口使用的。作为四线模式使用时,是一个全双工模式通信,输入和输出都是单向数据;而作为三线模式时,spi时一个半双工通信,也就是说用的同一根数据线进行输入输出,分时复用,所以我的代码里面用的是将mosi作为一个双向io;而另外一根miso线则是在三线模式中没有使用,我没太懂为啥文档里会把miso也作为一个双向的io。

下面是飞思卡尔的一个spi协议的模块框图:

      这个spi模块就划分为了几个大的部分,有控制寄存器,状态寄存器,时钟分频和移位寄存器。

verilog

先说一下代码,有很多不规范的地方,不同时钟最好放在不同module里面,等等,欢迎大家指正。
此为spi主设备,可挂外部设备进行通讯,以下为此模块特点:
1、传输帧数据位宽8、16、32位选择
2、对于外部从设备时钟SCLK频率,以PCLK为基础分频,可写入预分频系数进行分频;
3、cs为片选信号
4、支持三线半双工或四线全双工工作模式,二线的情况下通讯MOSI作为双向数据线;
5、可编数据发送顺序,MSB优先或LSB优先
6、SPI通讯忙标志
7、SPI频率计算公式(最小8分频)
8、SPI频率 = PCLK/(DIV * 2);
使用方式就是:先配置好时钟分频和spi模式,然后开启cs,最后开启start;(cs和start的间隔时间具体看从机器件规格书)
这是一个基于apb总线的模块,寄存器本想做一个表格,但是这上面表格里面换行不知道怎么弄就截图了。

这一段是时钟分频,sel0,sel1,sel2,sel3是四种模式,sclk的初始的电平不同:

//clk divide counter
always @(posedge clk or negedge rst_n)if(!rst_n)div_cnt <= 0;else if(div_cnt == div)div_cnt <= 0;else if(start_r2)div_cnt <= div_cnt + 31'b1;else div_cnt <= 0;//spi clk generate
always @(posedge clk or negedge rst_n)if(!rst_n)sclk <= 0;else if(start) beginif(div_cnt == div)sclk <= ~sclk;else ;end else begin if(sel0 || sel1)sclk <= 0;else if(sel2 || sel3)sclk <= 1;end

这一段是数据的移位,在每一个采样沿移位,给下一次数据做准备;其中ctrl【4:3】就是mode的选择,2‘b00代表sel0,2‘b01代表sel1,2’b10代表sel2,2’b11代表sel3;start_r2是延迟两拍的开始信号,ctrl【0】是开始信号的命令,lsb_en为1表示低位先出,否则为msb(高位先出)。

//shift data
always @(posedge clk or negedge rst_n)if(!rst_n)wr_data <= 0;else if(ctrl[0])    wr_data <= wr_r;else if(start_r2) beginif(lsb_en) begincase (ctrl[4:3])2'b00:beginif(sclk_pos || start_pos) tx_lsb_shift;else ;end2'b01:beginif(sclk_neg)tx_lsb_shift;else ;end2'b10:beginif(sclk_neg || start_pos) tx_lsb_shift;else ;end2'b11:beginif(sclk_pos)tx_lsb_shift;else ;endendcaseendelse begincase (ctrl[4:3])2'b00:beginif(sclk_pos || start_pos) tx_msb_shift;else ;end2'b01:beginif(sclk_neg)tx_msb_shift;else ;end2'b10:beginif(sclk_neg || start_pos) tx_msb_shift;else ;end2'b11:beginif(sclk_pos)tx_msb_shift;else ;endendcaseendendtask tx_lsb_shift;if(send_bits == 6'd8)wr_data = {1'b0, wr_data[7:1]};else if(send_bits == 6'd16)wr_data = {1'b0, wr_data[15:1]};else if(send_bits == 6'd32)wr_data = {1'b0, wr_data[31:1]};
endtasktask  tx_msb_shift;if(send_bits == 6'd8)wr_data = {wr_data[6:0], 1'b0};else if(send_bits == 6'd16)wr_data = {wr_data[14:0], 1'b0};else if(send_bits == 6'd32)wr_data = {wr_data[30:0], 1'b0};
endtask

这是一个状态机,主要用来生成标志信号,busy,read,write等。bidiroe 为1表示,选择三线模式(半双工模式),需要用读或者写来指定是发送还是接受数据,否则为四线模式(全双工);

//state machine
localparam  IDLE      = 6'b000_001,START     = 6'b000_010,WRITE     = 6'b000_100,READ      = 6'b001_000,FULL_WORK = 6'b010_000,END       = 6'b100_000;
reg     [5:0]current_state,next_state;
always @(posedge clk or negedge rst_n)if(!rst_n)current_state <= IDLE;else current_state <= next_state;always @(*)case(current_state)IDLE : beginif(start)next_state <= START;else next_state <= IDLE;endSTART : beginif(bidiroe )if(read_en)next_state <= READ;else next_state <= WRITE;else next_state <= FULL_WORK;endREAD : beginif(transfer_end)    next_state <= END;else next_state <= READ;endWRITE : beginif(transfer_end)    next_state <= END;else next_state <= WRITE;endFULL_WORK : beginif(transfer_end)    next_state <= END;else next_state <= FULL_WORK;endEND : beginnext_state <= IDLE;enddefault : next_state <= IDLE;endcasealways @(posedge clk or negedge rst_n)if(!rst_n) beginbusy      <= 0;wr_finish <= 0;rd_finish <= 0;recv_en   <= 0;write_en  <= 0;sdo_en    <= 0;endelse case(next_state)IDLE : beginbusy      <= 0;wr_finish <= 0;rd_finish <= 0;write_en  <= 0;recv_en   <= 0;sdo_en    <= 0;endSTART : beginbusy   <= 1;sdo_en <= 0;endREAD : beginrecv_en <= 1;sdo_en  <= 0;write_en<= 0;endWRITE : beginrecv_en <= 0;write_en<= 1;sdo_en  <= 1;endFULL_WORK : beginrecv_en <= 1;write_en<= 1;sdo_en  <= 1;end END : begincase({recv_en,write_en})2'b00 :beginrecv_en <= 0;write_en<= 0;wr_finish <= 0;rd_finish <= 0;end2'b01 : beginwr_finish <= 1;write_en <= 0;end2'b10 : beginrd_finish <= 1;recv_en <= 0;end2'b11 : beginrecv_en <= 0;write_en<= 0;wr_finish <= 1;rd_finish <= 1;endendcasesdo_en    <= 0;busy      <= 0;enddefault :beginbusy      <= 0;wr_finish <= 0;rd_finish <= 0;recv_en   <= 0;sdo_en    <= 0;end    endcase

这是数据接收部分,分为上升沿接收,和下降沿接收。

//recive data
//posedge sample
always @(posedge sclk or negedge rst_n)if(!rst_n)rd_data_pos <= 0;else if(recv_en)if(lsb_en) beginif(send_bits == 6'd8)rd_data_pos[7:0] <= {sdi, rd_data_pos[7:1]};else if(send_bits == 6'd16)rd_data_pos[15:0] <= {sdi, rd_data_pos[15:1]};else if(send_bits == 6'd32)rd_data_pos[31:0] <= {sdi, rd_data_pos[31:1]};else ;endelse beginif(send_bits == 6'd8)rd_data_pos[7:0]  <= {rd_data_pos[6:0], sdi};else if(send_bits == 6'd16)rd_data_pos[15:0] <= {rd_data_pos[14:0], sdi};else if(send_bits == 6'd32)rd_data_pos[31:0] <= {rd_data_pos[30:0], sdi};else ;end//negedge sample
always @(negedge sclk or negedge rst_n)if(!rst_n)rd_data_neg <= 0;else if(recv_en)if(lsb_en) beginif(send_bits == 6'd8)rd_data_neg[7:0] <= {sdi, rd_data_neg[7:1]};else if(send_bits == 6'd16)rd_data_neg[15:0] <= {sdi, rd_data_neg[15:1]};else if(send_bits == 6'd32)rd_data_neg[31:0] <= {sdi, rd_data_neg[31:1]};else ;endelse beginif(send_bits == 6'd8)rd_data_neg[7:0]  <= {rd_data_neg[6:0], sdi};else if(send_bits == 6'd16)rd_data_neg[15:0] <= {rd_data_neg[14:0], sdi};else if(send_bits == 6'd32)rd_data_neg[31:0] <= {rd_data_neg[30:0], sdi};else ;endalways @(*)if(sel0 || sel3)rd_data <= rd_data_pos;else if(sel1 || sel2)rd_data <= rd_data_neg;else rd_data <= 0;

数据发送部分,mode0和mode2在时钟沿来之前需要提前准备数据,这一段写的有点奇怪,但是找不到更好的描述方法只有这样了。

//send data
//mode0
always @(negedge sclk or posedge start_pos)if(start_pos) beginif(lsb_en)sdo0 <= wr_data[0];else begin if(send_bits == 6'd8)sdo0 <= wr_data[7];else if(send_bits == 6'd16)sdo0 <= wr_data[15];else if(send_bits == 6'd32)sdo0 <= wr_data[31]; endendelse if(sel0) beginif(lsb_en)sdo0 <= wr_data[0];else begin if(send_bits == 6'd8)sdo0 <= wr_data[7];else if(send_bits == 6'd16)sdo0 <= wr_data[15];else if(send_bits == 6'd32)sdo0 <= wr_data[31]; endendelse sdo0 <= 0;//mode1
always @(posedge sclk)if(sel1) beginif(lsb_en)sdo1 <= wr_data[0];else beginif(send_bits == 6'd8)sdo1 <= wr_data[7];else if(send_bits == 6'd16)sdo1 <= wr_data[15];else if(send_bits == 6'd32)sdo1 <= wr_data[31]; endendelse sdo1 <= 0;//mode2
always @(posedge sclk or posedge start_pos)if(start_pos) beginif(lsb_en)sdo2 <= wr_data[0];else beginif(send_bits == 6'd8)sdo2 <= wr_data[7];else if(send_bits == 6'd16)sdo2 <= wr_data[15];else if(send_bits == 6'd32)sdo2 <= wr_data[31]; endendelse if(sel2) beginif(lsb_en)sdo2 <= wr_data[0];else beginif(send_bits == 6'd8)sdo2 <= wr_data[7];else if(send_bits == 6'd16)sdo2 <= wr_data[15];else if(send_bits == 6'd32)sdo2 <= wr_data[31]; endendelse sdo2 <= 0;//mode3
always @(negedge sclk)if(sel3) beginif(lsb_en)sdo3 <= wr_data[0];else beginif(send_bits == 6'd8)sdo3 <= wr_data[7];else if(send_bits == 6'd16)sdo3 <= wr_data[15];else if(send_bits == 6'd32)sdo3 <= wr_data[31];end endelse sdo3 <= 0;always @(*)case (ctrl[4:3])2'b00: sdo <= start ? sdo0 : 1'b0;2'b01: sdo <= start ? sdo1 : 1'b0;2'b10: sdo <= start ? sdo2 : 1'b0;2'b11: sdo <= start ? sdo3 : 1'b0;endcase

综合出来的一个触发器,表示很新鲜。
最后就是一个三态逻辑,一般三态逻辑放到顶层,我原本是放在一个gpio的模块里面。这里给方便大家看,修改了一下,就放到这里了。

assign  sdi  = bidiroe  ? mosi : miso;
assign  mosi = bidiroe  ? (sdo_en ? sdo : 1'bz): sdo;

最后上仿真,由于是一个从cortexm0控制的,仿真文件是操作的寄存器。仿真文件看了没多大意义,我简单说一下,ctrl[0]是start信号,在拉高一个周期后就会自动清零。下面这个是测试三线模式,半双工模式,输入输出分时复用一个线,sdo_en忘了放上来了,sdo_en为1输出,否则输入。由于找了一下没找到仿真模型,就没挂,所以sdo_en为0的时候一直是高阻。
链接:代码下载地址(注释较少)

SPI协议主机verilog相关推荐

  1. FPGA作为从机与STM32进行SPI协议通信---Verilog实现

    一.SPI协议简要介绍 SPI,是英语Serial Peripheral Interface的缩写,顾名思义就是串行外围设备接口.SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用 ...

  2. SPI协议的verilog实现:利用spi协议配置寄存器

    状态机状态跳转图 因常常需要对寄存器进行配置,因而学习了V3学院的视频课,利用spi协议对寄存器进行配置,在此做个记录,以便日后回顾. 上图为状态机状态转移图,需要先将需要配置的寄存器的信息存放在RO ...

  3. 04【Verilog实战】SPI协议底层硬件接口设计(附源码RTL/TB)

    脚  本:makefile 工  具:vcs 和 verdi 写在前面 这个专栏的内容记录的是个人学习过程,博文中贴出来的代码是调试前的代码,方便bug重现. 调试后的程序提供下载,[下载地址] 发现 ...

  4. 基础SPI协议的FPGA实现(兼顾SPI4种模式)—Verilog

    简要说明 看见许多博主都有编写SPI协议,但是大都是对一个指定的时序进行FPGA的实现.于是就想编写一个比较常见的SPI通信协议,而且兼顾4种模式和不同数据长度.主要是用来对常见SPI协议的应用,和辅 ...

  5. Verilog——SPI协议

    SPI(Serial Peripheral Interface,串行外围设备接口),是Motorola公司提出的一种同步串行接口技术,是一种高速.全双工.同步通信总线,在芯片中只占用四根管脚用来控制及 ...

  6. Verilog功能模块——AXI4-Lite协议主机-单次写-使用FIFO

    一. 模块功能与应用场景 模块功能:AXI4-Lite协议主机,从FWFT FIFO(首字直通FIFO)中读取地址和数据,实现将数据写入到设定的地址中的操作. 二. 模块框图与使用说明 2.1 模块框 ...

  7. Verilog功能模块——AXI4-Lite协议主机-连续区间写-使用FIFO

    一. 模块功能与应用场景 模块功能:AXI4-Lite协议主机,从FWFT FIFO(首字直通FIFO)中不断读取数据,写入到一段地址中. 应用场景:DDR读写,BRAM读写等AXI接口的存储器件读写 ...

  8. FPGA实现的SPI协议(一)----SPI驱动

    写在前面 SPI协议系列文章: FPGA实现的SPI协议(一)----SPI驱动 FPGA实现的SPI协议(二)----基于SPI接口的FLASH芯片M25P16的使用 1.什么是SPI协议 SPI( ...

  9. FPGA作为从机与STM32进行SPI协议通信

    1.SPI协议简要介绍 (1)SPI,是英语Serial Peripheral Interface的缩写,顾名思义就是串行外围设备接口.SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上 ...

最新文章

  1. 面相对象的topSort和递推【ural1022/ural1017】
  2. Oracle Study之--Oracle 11g RAC设置归档路径错误案例
  3. php访问服务器文件路径,PHP与服务器文件系统的简单交互
  4. python从网址爬图片协程_python 用 gevent 协程抓取海量网页
  5. jQuery源码分析 Sizzle选择器
  6. linux手误rm可能不需要跑路
  7. 网游UI解决方案的选择(作者 鸣·铭)
  8. 如何突破织梦后台发布文章的250字符的内容摘要字数限制
  9. 拓端tecdat|R语言互联网金融下的中国保险业数据分析
  10. 基于Springboot的个人健康监控管理系统 毕业论文+项目源码、
  11. 2020 数学建模国赛 B 题参考思路
  12. 服务器系统报ata6,ATA_5.4考试管理系统使用手册[最终版].doc
  13. Android毕业设计选题依据,毕业设计选题依据、目的意义、
  14. 手机微信群控源码二次开发
  15. 苹果x充电慢是什么原因_苹果x无法激活维修引起这个故障有两个原因
  16. 微信设置水滴昵称,个性很漂亮,快试试!
  17. 以太网交换芯片及PHY处理相关
  18. android广告平台介绍
  19. 再一次打破局限:“华为云会议”和“畅连”到底连接了什么?
  20. Linux Top command

热门文章

  1. 【Windows】台式机cpu骤升100%
  2. 黑色诺言_蝴蝶——壁纸 Win7
  3. 《孩子快抓紧妈妈的手》
  4. 搭建Remix IDE本地开发环境
  5. 计算机休眠会断网,win10系统休眠断网的解决方法
  6. sparksql出现 serious problem at org.apache.hadoop.hive.ql.io.orc.OrcInputFormat.generateSplitsInfo
  7. 《Natural Language Processing with Python》读书笔记 001期
  8. 使用脚本更新阿里云DNS记录
  9. 第12章 从美国次贷危机中学到什么
  10. 丛林木马(数学 思维