使用verilog设计spi控制器

5 篇文章 0 订阅
4 篇文章 4 订阅

测试环境

操作系统:Windows10

综合仿真:Vivado 2018.3

芯片验证:Zynq7010

 

SPI模式

极性:CPOL     相位:CPHA

Mode0 CPOL=0, CPHA=0

Mode1 CPOL=0, CPHA=1

Mode2 CPOL=1, CPHA=0

Mode3 CPOL=1, CPHA=1

时钟极性CPOL: SPI空闲时,时钟信号SCLK的电平(1:空闲时高电平; 0:空闲时低电平)

时钟相位CPHA: SPI在SCLK第几个边沿采样数据(0:第一个边沿; 1:第二个边沿)

 

SPI时序分析

如下图,这是四种模式下的SPI时序,图中四种模式共用MISO:

从图中可以看出,MISO数据采样和MOSI数据输出都发生在同一时刻,只是它们的时钟不同。而仔细观察,4种模式都可以通过一种时钟变换得来,这里以模式3时钟为参考:

模式0时钟:对模式3时钟反相并移相半个周期

模式1时钟:对模式3时钟反相

模式2时钟:对模式3时钟移相半个周期

因此,4种模式我们可以不受SPI时钟的影响,只需要在固定的时间采样MISO数据及对MOSI时序做更新即可。

 

硬件描述

首先可以通过设置一个division来调节SPI时钟频率:

可以把SPI状态分为以下9种方式:

通过状态机来管理SPI的状态:

参考状态机及模式位生成SPI时钟:

参考状态机更新MOSI线:

根据参考时钟采样MISO数据:

最后检出一个finish脉冲信号:

完整代码:

/**
 * division分频系数计算公式: sclock = clock/2/(division+1)   =>    division = clock/2/sclock-1
 * 例如输入时钟clock为50M, 需要得到2M的spi时钟sclock, 则  division = 50000000/2/2000000-1  大约为 12
 * time: 2021-2-14
 * editor: huxiang hello
*/

/**
 * --------------- spi四种模式 ---------------------
 * 极性:CPOL     相位:CPHA
 * Mode0 CPOL=0, CPHA=0 
 * Mode1 CPOL=0, CPHA=1
 * Mode2 CPOL=1, CPHA=0 
 * Mode3 CPOL=1, CPHA=1
 * 时钟极性CPOL: 即SPI空闲时,时钟信号SCLK的电平(1:空闲时高电平; 0:空闲时低电平)
 * 时钟相位CPHA: 即SPI在SCLK第几个边沿采样数据(0:第一个边沿开始; 1:第二个边沿开始)
 */

module spi(
    input wire          clock,  // 时钟
    input wire          reset,  // 复位信号
    input wire  [31:0]  division,// 分频系数
    input wire  [1:0]   mode,   // spi模式
    output wire         sclock, // spi时钟引脚
    input wire          smiso,  // spi主入从出引脚
    output wire         smosi,  // spi主出从进引脚
    input wire  [7:0]   odata,  // 发送数据寄存器
    output reg  [7:0]   idata,  // 接收数据寄存器
    input wire          trigger,// 数据传输触发线 - 高电平触发
    output wire         finish  // 数据传输完成 - 高电平触发
    );
        
    // spi时钟分频计数
    reg [31:0]  ccount;
    // 每次clock_count计数完后发出一个高电平脉冲
    wire ccount_clear = (ccount == division) ? 1'b1 : 1'b0;
    always @(negedge clock)
    begin
        if(!reset || ccount_clear || trigger)
            ccount <= 32'd0;
        else
            ccount = ccount + 32'd1;
    end
    
    // spi状态参数
    localparam STATE_BIT0  = 4'd0;
    localparam STATE_BIT1  = 4'd1;
    localparam STATE_BIT2  = 4'd2;
    localparam STATE_BIT3  = 4'd3;
    localparam STATE_BIT4  = 4'd4;
    localparam STATE_BIT5  = 4'd5;
    localparam STATE_BIT6  = 4'd6;
    localparam STATE_BIT7  = 4'd7;
    localparam STATE_IDLE  = 4'd8;
    
    // spi状态机
    reg [4:0]   spi_state;          // 第0位每变化一次spi时钟跳变半个周期
    wire idle = spi_state[4];      // spi_state最高线就是idle线
    // 产生8个时序 - 此次的时钟复位和空闲时都是高电平
    always @(posedge clock)
    begin
        if(!reset)
            spi_state <= {STATE_IDLE, 1'b0};        // 复位后为空闲态
        else if(ccount_clear && !idle)
            spi_state <= spi_state + 5'd1;
        else if(trigger)
            spi_state <= {STATE_BIT0, 1'b0};       // 收到触发发送信号进入发送状态
        else;
    end
    
    // 根查表生成spi时钟(固定空闲高电平)
    wire [17:0] sclock_table = 18'b111010101010101010;
    wire sclock1 = sclock_table[spi_state];
    // 对sclock1移相半个周期
    reg sclock1_old;
    always @(posedge clock) sclock1_old = ccount_clear ? sclock1 : sclock1_old;
    // 根据模式设置选取时钟线 - 如果是在第一个沿采样数据,就选取慢半个周期的时钟线
    wire sclock2 = mode[0] ? sclock1 : sclock1_old;
    // sclock线, 根据模式设置spi输出时钟是否需要反相
    assign sclock = mode[1] ? sclock2 : !sclock2;
    
    // smosi线
    wire [8:0] mosi_hub = {1'b1, odata};
    assign smosi = mosi_hub[spi_state[4:1]];
    
    // 根据参考时钟来采样数据
    // smiso线
    always @(posedge sclock1 or negedge reset)
    begin
        if(!reset)
            idata <= 8'd0;       // 复位后接收寄存器清0
        else
            idata <= {smiso, idata[7:1]};   // 综合成移位寄存器
    end
    
    // 处理finish, - 使用old_idle延一拍,检出一个上升沿
    reg old_idle;
    always @(posedge clock)
    begin
        if(!reset)
            old_idle <= 1'b1;
        else
            old_idle <= idle;
    end
    assign finish = (!old_idle && idle) ? 1'b1 : 1'b0;

endmodule

如果需要通过AXI总线挂接到处理器上,可参考:使用AXI Lite总线将串口UART挂接到处理器

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值