找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1378

积分

0

好友

186

主题
发表于 5 天前 | 查看: 15| 回复: 0

本文将深入介绍另一种在嵌入式系统中广泛应用的同步串行通信协议——SPI(Serial Peripheral Interface)。SPI协议通常由一个主设备和一个或多个从设备构成,主设备负责发起并控制同步通信时序,从而完成数据交换。标准SPI协议仅使用4个引脚,因其接口简单、速率较高,常被用于FPGA或微处理器与Flash存储器、实时时钟、传感器及DSP等外设器件之间的通信。

SPI接口的四根信号线详解

  1. SSEL/CS:从设备片选使能信号。通常为低电平有效,当主设备将该引脚拉低时,对应的从设备被选中,主从设备之间方可进行通信。
  2. SCK:串行时钟信号,由主设备产生并输出,用于同步数据位传输。
  3. MOSI:主设备输出、从设备输入数据线。主设备通过此通道向从设备发送指令或数据。
  4. MISO:主设备输入、从设备输出数据线。主设备通过此通道读取从设备的状态或数据。

关键概念:CPOL与CPHA

为了正确配置SPI通信,必须理解两个关键参数:

  • CPOL (Clock Polarity):时钟极性。定义了SCK时钟线在空闲状态(非数据传输期间)的电平。若空闲时为高电平,则CPOL=1;若为低电平,则CPOL=0。
  • CPHA (Clock Phase):时钟相位。定义了数据采样的边沿。

通过对CPOL和CPHA进行组合,SPI共有四种不同的工作模式,以适应不同外设的时序要求。

SPI的四种工作模式

模式0:CPOL = 0, CPHA = 0。
SCK空闲时为低电平。数据在SCK的上升沿被采样,在下降沿发生变化。

SPI模式0时序图

模式1:CPOL = 0, CPHA = 1。
SCK空闲时为低电平。数据在SCK的下降沿被采样,在上升沿发生变化。

SPI模式1时序图

模式2:CPOL = 1, CPHA = 0。
SCK空闲时为高电平。数据在SCK的下降沿被采样,在上升沿发生变化。

SPI模式2时序图

模式3:CPOL = 1, CPHA = 1。
SCK空闲时为高电平。数据在SCK的上升沿被采样,在下降沿发生变化。

SPI模式3时序图

时序规律总结

  • CPHA=1:数据的输出发生在SCK第一个边沿,采样发生在第二个边沿。
  • CPHA=0:数据的采样发生在SCK第一个边沿,输出发生在第二个边沿。
  • CPOL=1:SCK第一个边沿为下降沿
  • CPOL=0:SCK第一个边沿为上升沿

在实际项目中,需要根据从设备的数据手册来确定应使用的SPI工作模式。理解网络协议的底层时序对于嵌入式开发和硬件设计至关重要。

FPGA实现:SPI主控制器Verilog代码示例

以下是一个SPI主控制器模块的Verilog实现代码,工作在模式0(CPOL=0, CPHA=0),系统时钟为100MHz,通过分频产生1MHz的SCK时钟。该设计采用状态机实现,是数字逻辑设计中控制时序逻辑的经典方法。

// 代码设计思路参考自《搭建你的数字积木》,已根据编码风格进行优化
module spi_master (
    input        mosi,
    input        clk,           // 100MHz系统时钟
    input        rst,
    input        busy,
    input        spi_send,      // 发送启动信号
    input  [7:0] spi_data_out,  // 待发送的8位数据
    output reg   sck,           // SPI时钟输出
    output reg   miso,          // SPI数据输出(主->从)
    output reg   cs,            // 片选信号
    output reg   spi_send_done  // 发送完成标志
);

    reg [3:0] count;
    parameter IDLE   = 0;
    parameter CS_L   = 1;
    parameter DATA   = 2;
    parameter FINISH = 3;

    reg [4:0] cur_st;
    reg [4:0] nxt_st;
    reg [7:0] reg_data;      // 数据移位寄存器
    reg       sck_reg;       // 1MHz时钟寄存器
    reg [8:0] delay_count;   // 分频计数器 (100MHz/1MHz/2 = 50)

    // 分频计数器,用于产生1MHz时钟的使能信号
    always @(posedge clk or negedge rst) begin
        if (~rst)
            delay_count <= 0;
        else if (delay_count == 49)
            delay_count <= 0;
        else
            delay_count <= delay_count + 1;
    end

    // 产生1MHz的sck_reg时钟信号
    always @(posedge clk or negedge rst) begin
        if (~rst)
            sck_reg <= 0;
        else if (delay_count == 49)
            sck_reg <= ~sck_reg;
    end

    // 组合逻辑,根据状态控制最终的sck输出
    always @(*) begin
        if (cs)
            sck = 1;
        else if (cur_st == FINISH)
            sck = 1;
        else if (!cs)
            sck = sck_reg;
        else
            sck = 1;
    end

    // 状态寄存器更新
    always @(posedge sck_reg or negedge rst) begin
        if (~rst)
            cur_st <= IDLE;
        else
            cur_st <= nxt_st;
    end

    // 下一状态逻辑 (状态转移)
    always @(*) begin
        nxt_st <= cur_st;
        case (cur_st)
            IDLE:   if (spi_send) nxt_st = CS_L;
            CS_L:   nxt_st = DATA;
            DATA:   if (count == 7) nxt_st = FINISH;
            FINISH: if (busy) nxt_st = IDLE;
            default: nxt_st = IDLE;
        endcase
    end

    // 发送完成信号产生逻辑
    always @(*) begin
        if (~rst)
            spi_send_done = 0;
        else if (cur_st == FINISH)
            spi_send_done = 1;
        else
            spi_send_done = 0;
    end

    // 片选信号(cs)产生逻辑
    always @(posedge sck_reg or negedge rst) begin
        if (~rst)
            cs <= 1;
        else if (cur_st == CS_L)
            cs <= 0;
        else if (cur_st == DATA)
            cs <= 0;
        else
            cs <= 1;
    end

    // 数据位计数器
    always @(posedge sck_reg or negedge rst) begin
        if (~rst)
            count <= 0;
        else if (cur_st == DATA)
            count <= count + 1;
        else if (cur_st == IDLE | cur_st == FINISH)
            count <= 0;
    end

    // 数据移位输出逻辑 (在sck下降沿操作,符合模式0时序)
    always @(negedge sck_reg or negedge rst) begin
        if (~rst) begin
            miso <= 0;
            reg_data <= 0;
        end
        else if (cur_st == DATA) begin
            // 寄存器左移,输出最高位
            reg_data[7:1] <= reg_data[6:0];
            miso <= reg_data[7];
        end
        else if (spi_send) begin
            // 发送命令到来时,锁存待发送数据
            reg_data <= spi_data_out;
        end
    end

endmodule



上一篇:阿里云CDN免费加速长期使用技巧:为个人网站设置续费规则至2050年
下一篇:ESP32-S2实战:WiFi与FM双模收音机系统设计与开发教程
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 17:18 , Processed in 0.238284 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表