EM9280系列SPI接口应用方案

 2014-7-28         

  EM9280系列产品包括EM9280、EM9287和EM9281,是英创公司新一代的低成本嵌入式主板产品。该主板的SPI接口,在内部DMA(Direct Memory Access直接内存存取)机制的驱动下,其最高数据传输速度可达20Mbps。另外SPI接口可支持4bit、8bit、16bit位长的数据通讯;也可对SPI时序的极性及相位进行设置。

 

  针对SPI接口的应用特点,EM9280的SPI的驱动进行了专门的优化,不仅可支持常规的SPI读、写操作,还可支持外部中断触发的读写操作。中断触发的读写操作,主要应用于工业控制的高速数据采集。另一方面,针对AD芯片控制需求,SPI驱动还支持混合读写模式的数据传输操作。

 

  本文以下部分重点介绍SPI驱动API的使用方法。

 

操作SPI设备的基本步骤

 

  1、打开SPI设备文件,其设备文件名为“SPI1:”
  2、根据应用需求,设置SPI数据帧的基本参数,包括数据长度、波特率、时钟极性等参数。
  3、若需要用到外部中断触发SPI读取操作,则需要设置外部GPIO中断管脚,及中断后的读取数据的长度。
  4、设置完成后,对常规操作,即可使用标准的ReadFile函数接收SPI数据、使用WriteFile发送SPI数据。
  5、对需要读写混合操作的,则需要调用DeviceIoControl来实现。
  6、当启动了外部中断,则通过调用DeviceIoControl来等待外部事件,然后再调用ReadFile函数来读取已缓冲在驱动程序内部的SPI数据。
  7、调用CloseHandle将关闭SPI接口并清除相关设置。即使重新打开SPI设备文件,需重新设置SPI的参数,才能进行读写。

 

SPI数据帧参数设置

 

  初始化SPI,需要用到下面这个数据结构:
  typedef struct _SPIFrame
  {
    UCHAR ucBitLength; // SPI数据bit长度,= 4、8、16
    DWORD dwBitRate; // SPI波特率,20000000对应20Mbps
    BOOL bPhase; // 时钟相位
    BOOL bPolarity; // 时钟极性
  } SPIFrame , *PSPIFrame;

 

  该数据结构在hw_spi.h头文件中进行的定义,数据结构中的变量说明:
  ucBitLength:SPI通讯的数据位长,EM9280/EM9287支持4bit、8bit、16bit三种数据位长格式,在hw_spi.h中定义了这三种数据位长的常量。
  dwBitRate:SPI时钟速率,为每秒传输的bit数,参数20000000表示20Mbps,
  bPhase:SPI时序相位设置(如下图所示)
  bPolarity:SPI时序极性设置(如下图所示)

 

bPhase=0 , bPolarity=0

 

bPhase=1 , bPolarity=0

 

bPhase=0 , bPolarity=1

 

bPhase=1 , bPolarity=1

 

SPI设备的初始化例子

 

  HANDLE hSPI;
  SPIFrame ConfigSPI;

  // 打开设备驱动文件
  hSPI = CreateFile(L”SPI1:”, // name of device
    GENERIC_READ|GENERIC_WRITE, // desired access
    FILE_SHARE_READ|FILE_SHARE_WRITE, // sharing mode
    NULL, // security attributes (ignored)
    OPEN_EXISTING, // creation disposition
    FILE_FLAG_RANDOM_ACCESS, // flags/attributes
    NULL); // template file (ignored)

  if(hSPI == FALSE )
  {
    printf('SPI Open False!!!\r\n');
    return 0;
  }

  // 配置SPI参数
  ConfigSPI.ucBitLength=SSP_WORD_LENGTH_8BITS; // Len_8BITS
  ConfigSPI.dwBitRate=10000000; // 10Mbps
  ConfigSPI.bPhase=0;
  ConfigSPI.bPolarity=0;
  DeviceIoControl(hSPI, // file handle to the driver
    SPI_IOCTL_SSPCONFIGURE, // I/O control code
    &ConfigSPI, // in buffer
    sizeof(ConfigSPI), // in buffer size
    NULL,
    0,
    NULL,
    NULL)

 

SPI接口的单向读写操作

 

  用标准的ReadFile和WriteFile就可实现常规的SPI数据接收(读)或发送(写)。

 

  SPI数据接收的函数调用:
  ReadFile(hSPI, // 设备驱动文件句柄
  pDatBuf, // 数据buffer指针,注意指针类型!
  dwBufLength, // 数据buffer的字节长度
  pdwBytesRead, // 实际读取的SPI数据字节数
  NULL)

 

  pDatBuf:数据BUFF指针。需要注意的是SPI数据帧长度若为4-bit或8-bit,则每个SPI数据占用一个字节,而对16-bit的SPI数据,则占用2个字节。一般来说,对4-bit或8-bit的SPI传输,其数据buffer应当是BYTE类型的;对16-bit的SPI传输,数据buffer则为WORD类型的。

  dwBufLength:需要传输的数据字节长度。该参数是以字节为单位,其涵义也与SPI数据长度有关,对16-bit的SPI传输,dwBufLength应为2的倍数。

  pdwBytesRead:SPI数据实际接收的字节数。一个正确的SPI数据接收调用后,指针pdwByteRead所包含的数据应等于dwBufLength,才能表示SPI数据接收执行完全正确。

 

  SPI数据发送的函数调用:
  WriteFile(hSPI, // 设备驱动文件句柄
  pDatBuf, // 数据buffer指针,事先应把数据填入
  dwBufLength, // 数据buffer的字节长度
  pdwBytesWritten, // 实际发送的SPI数据字节数
  NULL)

 

  发送函数的参数定义与接收函数的参数定义是一致的。特别的,一个正确的SPI数据发送调用后,指针pdwByteWritten所包含的数据应等于dwBufLength。

 

读写混合型的SPI操作

 

  在SPI的实际应用,有时需要在一个连续的片选过程中,既有读操作,也有写操作。这时间需要用到所谓的混合型SPI操作。

 

  混合型SPI操作需要用到以下数据结构:
  typedef struct _SPITransfer
  {
    LPVOID pTxBuff; // SPI发送buffer指针
    LPVOID pRxBuff; // SPI接收buffer指针
    DWORD dwBufLength; // 本次SPI传输的字节数
  } SPITransfer;

 

  pTxBuff:SPI输出数据BUFF指针
  pRxBuff:SPI读入数据BUFF指针
  dwBufLength:SPI数据传输长度,以字节为单位

 

  注意,EM9280的SPI接口仅支持半双工操作,因此在上述结构中,只能有一个buffer指针为有效指针,另一个必须为NULL。dwBufLength的定义与单向读写的定义一致。具体的传输是通过DeviceIoControl来实现的,举例说明,本例首先进行发送1个字节(8-bit SPI),然后接收2个字节。

 

  SPITransfer Trans[2];
  BYTE Tx[16], Rx[16]; // buffer足够大

  Tx[0] = 0xE5; // 发送的字节
  Trans[0].pTxBuf = Tx;
  Trans[0].pRxBuf = NULL;
  Trans[0].dwBufLength = 1; // 要发送1字节
  Trans[1].pTxBuf = NULL;
  Trans[1].pRxBuf = Rx;
  Trans[1].dwBufLength = 2; // 要接收2字节

  DeviceIoControl(hSPI,
    SPI_IOCTL_EXCHANGE,
    Trans, // in buffer
    sizeof(Trans) , // in buffer size
    NULL,
    0,
    NULL,
    NULL))

 

  在上述调用中需要注意的是,DeviceIoControl()输入参数中的buffer长度必须是数据结构SPITransfer大小的整倍数,否则将被视作无效参数。

 

外部中断触发的SPI操作

 

  外部中断触发的SPI操作,主要是利用SPI的高速特性,进行实时的大数据量读取。因为SPI的接线非常简单,作为一种高效低成本的接口模式在工业控制领域有广泛的应用。使用这种SPI操作方式,需要用到以下数据结构:
  typedef struct _SPI_IrqTransfer
  {
    DWORD dwGpioPin; // 外部中断管脚,上升沿触发中断
    DWORD dwBufLength; // 中断触发的SPI传输的字节数,小于64KB
    DWORD dwRVSD; // 保留,必须设置为0
  } SPI_IrqTransfer;

 

  dwGpioPin:要用作外部中断源的GPIO引脚
  dwBufLength:要读取的数据字节长度
  dwRVSD:系统保留,必须设置为0

 

  在上述结构中,dwBufLength的定义与单向读写的定义一致,如果dwGpioPin与dwBufLength同时设置为0,则将关闭已打开的GPIO中断资源并禁止该功能启动。dwGpioPin为EM9280主板的GPIO引脚编号,与GPIO操作时的引脚数据一致。注意:由于系统功能的占用,不是所有的GPIO引脚都可以用作外部中断触发源。

 

   EM9280可以使用的GPIO引脚有:GPIO0、GPIO1、GPIO6、GPIO7、GPIO10、GPIO11、GPIO20、GPIO21、GPIO22、GPIO23。

   EM9287和EM9281可以使用的GPIO引脚有:GPIO0 - GPIO23。

 

  该操作的具体的设置操作仍然需要调用DeviceIoControl()来实现。
  SPI_IrqTransfer irq_transfer;
  irq_transfer. dwGpioPin=GPIO0; // 使用GPIO0作为SPI的外部中断源
  irq_transfer. dwBufLength=1024; // 中断产生后需要读取1024字节的数据
  irq_transfer. dwRVSD=0;
  DeviceIoControl(hSPI,
    SPI_IOCTL_SSP_IRQTransfer,
    & irq_transfer, // 输入参数
    sizeof(SPI_IrqTransfer), // 输入参数字节数
    NULL,
    0,
    NULL,
    NULL);

 

  设置完成即启动外部中断自动触发SPI操作,一旦中断产生,驱动程序将自动接收dwBufLength长度的数据,存储在驱动程序的内部缓冲区中。数据接收完成后,将发送事件通知应用层。应用程序可通过DeviceIoControl()调用来等待该事件,得到事件后再调用ReadFile读取数据。通过调用DeviceIOControl()等待SPI事件,可以给定一个时间参数作为等待超时的条件,以ms为单位。成功等到SPI执行完成的消息时,DeviceIoControl会返回TRUE,否则返回FALSE。SPI事件等待的调用方法如下:
  DeviceIoControl(hSPI,
    SPI_IOCTL_SSP_WaitSPIEvent,
    &DelayTime, // 等待超时,时间为ms
    Sizeof(DWORD),
    NULL,
    0,
    NULL,
    NULL)

 

  调用上述方法启动了外部中断触发SPI读取数据的功能后,该功能将一直存在,即每次在所设置的GPIO引脚上产生中断信号,都会执行一次SPI读取操作,直到应用程序关闭该中断,即设置dwGpioPin和dwBufLength等于0,再调用DeviceIoControl()进行设置操作。

 

  SPI操作相关的范例代码请参考光盘中的EM9280_SPIDemo,或来邮件索取或咨询。