SPI 总线

更新时间:
2024-03-14
下载文档

SPI 总线

SPI(Serial Peripheral Interface),串行外设接口,是 Motorola 提出的一种高速的、全双工、同步的通信总线。SPI 接口主要用于 MCU 与外围设备的通信,外围设备包括 EEPROM、FLASH、实时时钟、AD 转换器、数字信号处理器和数字信号解码器等。

它以主从方式工作,通常有一个主设备和一个或多个从设备。

SPI 在芯片的管脚上只占用四根线,分别是 MOSI(数据输出),MISO(数据输入),SCLK(时钟),CS(片选)。

MOSI:主机数据输出,从机数据输入;

MISO:主机数据输入,从机数据输出;

SCLK:时钟信号,由主机产生;

CS:从机使能信号,由主机控制。

SPI 总线原理

SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个“发送者(Transmitter)”或者“接收者(Receiver)”。实质上每次 SPI 的数据传输都是主从设备在交换数据。SPI 总线的传输中,CS 信号是低电平有效的,当我们要与某外设通信的时候,需要将该外设上的 CS 线置低。在数据传输的过程中,每次接收到的数据必须在下一次数据传输之前被采样。如果之前接收到的数据没有被读取,那么这些已经接收完成的数据将有可能会被丢弃,导致 SPI 物理模块最终失效。因此,SPI 在传输完数据后,必须读取 SPI 设备里的数据,即使这些数据在程序里是无用的。

SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性 CPOL (Clock Polarity)和相位 CPHA(Clock Phase)可以配置。CPOL,表示 SCLK 空闲的时候,其电平值是低电平还是高电平;CPHA,表示数据采样在第一个跳变沿还是第二个跳变沿。

CPHA=0,表示第一个边沿:

对于 CPOL=0,SCLK 空闲时是低电平,第一个跳变沿就是从低变到高,所以是上升沿;

对于 CPOL=1,SCLK 空闲时是高电平,第一个跳变沿就是从高变到低,所以是下降沿;

CPHA=1,表示第二个边沿:

对于 CPOL=0,SCLK 空闲时是低电平,第二个跳变沿就是从高变到低,所以是下降沿;

对于 CPOL=1,SCLK 空闲时是高电平,第一个跳变沿就是从低变到高,所以是上升沿;

SylixOS SPI 总线框架分析

SylixOS 的 SPI 体系结构和 I2C 类似,也分为 3 个组成部分。

  • SPI 核心

SPI 核心提供了 SPI 总线驱动和设备驱动的注册、注销方法,SPI 通信方法,适配器无关的代码等。每个 SPI 从设备驱动都能找到和它相连的 SPI 总线适配器。

  • SPI 总线驱动

SPI 总线驱动主要包括 SPI 适配器结构 lw_spi_adapter 和 SPI 适配器的通信方法数据结构。

  • SPI 设备驱动

SPI 设备驱动是对 SPI 设备端的实现,设备一般挂接在受CPU控制的 SPI 适配器上,通过 SPI 适配器与 CPU 交换数据。每一条 SPI 总线对应一个 SPI 适配器。在 内核中,每一个适配器提供了一个描述的结构,也定义了 Adapter 支持的操作。再通过 SPI 核心层将 SPI 设备与 SPI 适配器关联起来。

SPI 总线适配器相关信息位于“libsylixos/SylixOS/system/device/spi”下,其适配器创建原型如下:

#include <SylixOS.h>
INT  API_SpiAdapterCreate (CPCHAR             pcName, 
                           PLW_SPI_FUNCS      pspifunc)

函数 API_SpiAdapterCreate 原型分析:

此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR

  • 参数 pcName 是 SPI 适配器的名称,即 shell 命令 buss 显示的名称。
  • 参数 pspifunc 是 SPI 总线传输函数的指针。

函数 API_SpiAdapterCreate 使用结构体 PLW_ SPI _FUNCS 来向内核提供传输函数集合,其详细描述如下:

#include <SylixOS.h>
typedef struct lw_spi_funcs {
    INT         (*SPIFUNC_pfuncMasterXfer)(PLW_SPI_ADAPTER     pspiadapter,
                                           PLW_SPI_MESSAGE     pspimsg,
                                           INT                 iNum);
    INT         (*SPIFUNC_pfuncMasterCtl)(PLW_SPI_ADAPTER      pspiadapter,
                                          INT                  iCmd,
                                          LONG                 lArg);
} LW_SPI_FUNCS;
typedef LW_SPI_FUNCS        *PLW_SPI_FUNCS;
  • SPIFUNC_pfuncMasterXfer :SPI 传输函数,SPI 设备会直接调用此函数实现消息发送。第一个参数 pspiadapter 为 SPI 总线适配器指针,第二个参数 pspimsg 为 SPI 设备需要传输的消息结构体首地址指针,第三个参数 iNum 为需要传输的消息个数,以上三个参数即可告知 SPI 设备如何调用此函数实现消息传输。
  • SPIFUNC_pfuncMasterCtl :SPI 适配器控制函数,用来实现与硬件控制器相关的控制。第一个参数 pspiadapter 为 SPI 总线适配器指针,第二个参数 iCmd 为控制命令,第三个参数 lArgiCmd 相关。

注册到内核的传输函数集合中要用到多种结构体, PLW_SPI_ADAPTER 总线适配器结构体指针主要包含当前总线适配器节点信息, PLW_SPI_DEVICE 总线设备结构体指针主要包含当前SPI设备的相关信息, PLW_SPI_MESSAGE 消息请求结构体指针作用是指向需要发送的消息缓冲区,提供以上三种结构体后控制器即可知道如何进行发送,各种结构体的详细描述如下:

首先介绍 SPI 总线适配器结构体,该结构体详细描述如下:

#include <SylixOS.h>
typedef struct lw_spi_adapter {
    LW_BUS_ADAPTER           SPIADAPTER_pbusadapter;       /*  总线节点              */
    struct lw_spi_funcs     *SPIADAPTER_pspifunc;          /*  总线适配器操作函数      */
    LW_OBJECT_HANDLE         SPIADAPTER_hBusLock;          /*  总线操作锁             */
    LW_LIST_LINE_HEADER      SPIADAPTER_plineDevHeader;    /*  设备链表               */
} LW_SPI_ADAPTER;
typedef LW_SPI_ADAPTER      *PLW_SPI_ADAPTER;
  • SPIADAPTER_pbusadapter :系统总线节点结构体。
  • SPIADAPTER_pi2cfunc :指向总线适配器的操作函数,即 API_SpiAdapterCreate 函数注册到核心层的操作函数集指针。
  • SPIADAPTER_hBusLock :SPI 总线锁,不需要手动处理。
  • SPIADAPTER_plineDevHeader :指向此适配器下挂载的设备链表,不需要手动处理。

SPI 设备结构体详细描述如下:

#include <SylixOS.h>
typedef struct lw_spi_device {
    PLW_SPI_ADAPTER         SPIDEV_pspiadapter;                        /*  挂载的适配器         */
    LW_LIST_LINE            SPIDEV_lineManage;                         /*  设备挂载链          */
    atomic_t                SPIDEV_atomicUsageCnt;                     /*  设备使用计数        */
    CHAR                    SPIDEV_cName[LW_CFG_OBJECT_NAME_SIZE];     /*  设备的名称          */
} LW_SPI_DEVICE;
typedef LW_SPI_DEVICE    *PLW_SPI_DEVICE;
  • SPIDEV_pi2cadapter :设备挂载的 SPI 总线适配器。
  • SPIDEV_lineManage :设备挂载的链表。
  • SPIDEV_atomicUsageCnt :设备使用的计数。
  • SPIDEV_cName :设备名称。

SPI 消息结构体是 SPI 主机和从机通信的消息格式,该结构体的详细描述如下:

#include <SylixOS.h>
typedef struct lw_spi_message {
    UINT16          SPIMSG_usBitsPerOp;           /*  操作单位bits数         */
    UINT16          SPIMSG_usFlag;                /*  传输控制              */
    UINT32          SPIMSG_uiLen;                 /*  长度(缓冲区大小) 长度为0, 只设置传输控制参数     */
    UINT8          *SPIMSG_pucWrBuffer;           /*  发送缓冲区            */
    UINT8          *SPIMSG_pucRdBuffer;           /*  接收缓冲区            */
    VOIDFUNCPTR     SPIMSG_pfuncComplete;         /*  传输结束后的回调函数    */
    PVOID           SPIMSG_pvContext;             /*  回调函数参数          */
} LW_SPI_MESSAGE;
typedef LW_SPI_MESSAGE      *PLW_SPI_MESSAGE;
  • SPIMSG_usBitsPerOp :操作单位 bits 数。
  • SPIMSG_usFlag :传输控制参数,其取值见下表。
  • SPIMSG_usLen :存放消息内容的缓存区长度,若长度为 0,只设置传输控制参数。
  • SPIMSG_pucWrBuffer :发送缓存区。
  • SPIMSG_pucusRdBuffer :接收缓存区。
  • SPIMSG_pfuncComplete :传输结束后的回调函数。
  • SPIMSG_pvContext :回调函数参数。
传输控制参数取值含义
LW_SPI_M_CPOL_0CPOL 配置 0
LW_SPI_M_CPOL_1CPOL 配置 1
LW_SPI_M_CPHA_0CPHA 配置 0
LW_SPI_M_CPHA_1CPHA 配置 1
LW_SPI_M_CPOL_EN是否设置新的 CPOL 配置
LW_SPI_M_CPHA_EN是否设置新的 CPHA 配置
LW_SPI_M_WRBUF_FIX发送缓存区仅发送第一个字节
LW_SPI_M_RDBUF_FIX接收缓存区仅接收第一个字节
LW_SPI_M_MSB从高位到低位
LW_SPI_M_LSB从低位到高位

SPI flash 代码分析

一个具体的 SPI 设备驱动以 lw_spi_device 结构体的形式进行组织,用于将设备挂接于 SPI 总线,组织好了后,再完成设备本身所属类型的驱动。下面以 SPI flash 为例介绍 SPI 设备驱动的实现。

SPI flash 是一个挂载在 SPI 总线上的设备,它依靠 SPI 总线的传输函数传递数据。所以在 SPI flash 设备创建时,要调用 API_SpiDeviceCreate 函数将其挂载在一个指定的 SPI 总线适配器上,具体实现如下程序清单所示。函数 API_SpiDeviceCreate 原型如下:

#include <SylixOS.h>
PLW_SPI_DEVICE  API_SpiDeviceCreate (CPCHAR  pcAdapterName,
                                     CPCHAR  pcDeviceName)

函数 API_I2cDeviceTransfer 原型分析:

  • 此函数成功返回 pspidevice (SPI 设备结构体类型),失败返回 LW_NULL
  • 参数 pcAdapterName 是设备挂载的 SPI 适配器名称。
  • 参数 pcDeviceName 是设备名称。

函数 API_SpiDeviceBusRequest 的作用是获得指定 SPI 设备的总线使用权,原型如下:

#include <SylixOS.h>
INT  API_SpiDeviceBusRequest (PLW_SPI_DEVICE  pspidevice)

函数 API_SpiDeviceBusRequest 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR
  • 参数 pspidevice 是 SPI 设备结构体。

函数 API_SpiDeviceBusRelease 的作用是释放指定 SPI 设备的总线使用权,原型如下:

#include <SylixOS.h>
INT  API_SpiDeviceBusRelease (PLW_SPI_DEVICE  pspidevice)

函数 API_SpiDeviceBusRelease 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR
  • 参数 pspidevice 是 SPI 设备结构体。

函数 API_SpiDeviceCtl 的作用是指定 SPI 设备处理指定命令,原型如下:

#include <SylixOS.h>
INT  API_SpiDeviceCtl (PLW_SPI_DEVICE  pspidevice, INT  iCmd, LONG  lArg)

函数 API_SpiDeviceCtl 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR
  • 参数 pspidevice 是 SPI 设备结构体。
  • 参数 iCmd 是 SPI 传输控制命令。
  • 参数 lArg 是 SPI 传输控制命令的参数。
#include <SylixOS.h>
INT  norDevCreate (PCHAR  pSpiBusName, UINT  uiSSPin)
{
    INT               iError;
    __PSPI_NOR_OBJ  pSpi_Nor = &_G_spiNorObj;
    if (pSpi_Nor->pSpiNorDev == NULL) {
        pSpi_Nor->pSpiNorDev = API_SpiDeviceCreate(pSpiBusName, SPI_NOR_DEVNAME);
    }
    __ssGpioInit(uiSSPin); 
    iError = API_SpiDeviceBusRequest(pSpi_Nor->pSpiNorDev);
    if (iError == PX_ERROR) {
        NOR_DEBUG("Spi Request Bus error\n\r");
        return (PX_ERROR);
    }
    API_SpiDeviceCtl(pSpi_Nor->pSpiNorDev, SPI_MODE_SET, SPI_MODE_0);   
    /* 
     * 设置SPI模式为MODE 0          
     */
    API_SpiDeviceCtl(pSpi_Nor->pSpiNorDev, BAUDRATE_SET, BAUDRATE_MAX / 4);        /* 设置波特率      */
    API_SpiDeviceCtl(pSpi_Nor->pSpiNorDev, SPI_QUADM_CANCLE, 0);
    /*  
     *  取消4线模式设置    
     */
    SPI_ENABLE_SS();
    __flashNorCmd1Byte(EQPI);
    SPI_DISABLE_SS();
    API_SpiDeviceCtl(pSpi_Nor->pSpiNorDev, SPI_QUADM_SET, 0);
    /*        
     *  设置4线模式
     */
    API_SpiDeviceBusRelease(pSpi_Nor->pSpiNorDev);
    /* 
     *  释放SPI总线          
     */
    return  (ERROR_NONE);
}

如下所示,SPI flash 需要调用 SPI 的传输函数 API_SpiDeviceTransfer 来完成数据的传输,函数 API_SpiDeviceTransfer 的原型如下:

#include <SylixOS.h>
INT  API_SpiDeviceTransfer (PLW_SPI_DEVICE   pspidevice, 
                            PLW_SPI_MESSAGE  pspimsg,
                            INT              iNum)

函数 API_SpiDeviceTransfer 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR
  • 参数 pspidevice 是 SPI 设备结构体。
  • 参数 pspimsg 是 SPI 传输消息结构体组。
  • 参数 iNum 是 SPI 传输消息数量。
#include <SylixOS.h>
static INT  __flashNorCmd1Byte (UINT8 ucCmd)
{
    UINT8            ucTxCmd[1]       = {ucCmd};
    LW_SPI_MESSAGE   spiCmdMessage  = {
            .SPIMSG_uiLen             = 1,
            .SPIMSG_pucWrBuffer       = ucTxCmd,
            .SPIMSG_pucRdBuffer       = NULL,
    };
    API_SpiDeviceTransfer(_G_spiNorObj.pSpiNorDev, &spiCmdMessage, 1);  
    /*  
     *  发起传输
     */
    return  (ERROR_NONE);
}
文档内容是否对您有所帮助?
有帮助
没帮助