2013-10-17

Coocox SPI learning notes

Coocox 学习系列之CoX_SPI篇 - Coocox Liamjeal 2012-9-13

http://www.geek-workshop.com/thread-1902-1-1.html

1. SPI接口设计思想

1.1 CoX.SPI发展过程,历史版本

     CoX1.0版本下的CoX.SPI是随着CoX第一版于2009年诞生而开始的,在第一版中,我们首先实现了SPI模块的主从模式的选择和工作状态的配置,单个字符的发送和接收以及数据流的发送和接收等功能。同样我们也采用面向对象的方式,定义了一个通用的SPI的结构体

typedef struct 
{
  COX_Status (*Init)       (uint8_t mode,     uint32_t rate);      
  uint32_t   (*Write)      (const void *wbuf,  uint32_t wlen);
  uint32_t   (*Read)      (void *rbuf,        uint32_t rlen);
  uint32_t   (*ReadWrite)  (uint32_t wdat);  
  COX_Status (*Cfg)       (uint8_t index,     uint32_t arg,    uint32_t *pre_arg);  
} COX_SPI_PI_Def;

然后再实现基于CoX.SPI的对应不同厂商MCU的函接口数。

当我们在实际编程中需要某个功能的时候,只需调用对应的CoX接口函数就可以了,而且由于CoX上层的函数名称的一致性,以利于系统将来的可移植性。有兴趣的可以参考Coocox官网中NUC140-LB Board中的CoOS例程,链接地址:

http://www.coocox.com/downloadfile/CoOS/Demo/NUC140_CoOS.zip。

然而,在后来的开发使用过程中,发现CoX1.0中SPI定义有几个明显的不足:

1. 实现过程中考虑情况不全面。比如在第一版中SPI读取和发送数据的方式只有阻塞式,这样在遇到系统不需要强制读取和发送数据的时候就无能为力了,同时还缺乏一些其他特殊的功能,比如SSn管脚配置的功能。

2. 大量采用结构体的形式,使得代码的可读性大大降低,同时也降低了代码的重用性,效率也不高。

3. CoX API封装不全面,仅仅实现了SPI初始化、配置、发送与接收的功能,在实现一些其它基本的功能时,仍然需要结合对应的厂商库来使用。

鉴于CoX.SPI以上的几种缺陷,经常一年多时间的积累,在2011年开始推出了CoX 2.0版本,这个版本解决了上述所有的缺点的同时,还保留了CoX设计的初衷——那就是在Cortex-M系列CPU上面的通用性。

下面,详细介绍2.0版本下的CoX.SPI接口。

2. CoX2.0的SPI接口定义

SPI通信模块提供了用来处理器件和外围设备的串行通信,可以配置成使用 Motorola SPI 、National Semiconductor  Microwire 或 Texas Instrument同步串  行接口的帧格式。数据帧的大小也可以配置。 SPI 模块对接收到的外围设备的数据执行串行-并行转换,对发送给外围设备的数据执行 并行-串行转换,TX 和 RX 通路由内部 FIFO 进行缓冲。 SPI 模块可以配置成一个主机或一个从机设备。作为一个从机设备,SPI 模块还能配置 成禁止它的输出,这就允许一个主机设备与多个从机设备相连。 SPI 模块还包含一个可编程的位速率时钟分频器和预分频器来产生输出串行时钟(从 SPI 模块的输入时钟获得)。产生的位速率取决于输入时钟和连接的外设支持的最大位速率。 对于包含一个 DMA 控制器的器件,SPI 模块也提供一个 DMA 接口以便通过 DMA 来 实现数据传输。

在设计SPI的CoX接口的时候,我们按着SPI的功能划分了几个模块,具体的设计思想如下:

(1) SPI 工作状态配置接口

主模式/从模式的选择

数据流传输速率、数据宽度、数据移位方向的设置

SSn管脚状态的设置

DMA模式的选择

(2) SPI 中断控制接口

使能/失能中断

中断服务程序

(3) SPI 数据发送与接收处理接口

启动SPI数据发送和接收

SPI数据发送与接收处理

在设计CoX2.0-SPI的接口时,我们充分考虑了基于 Cortex-M 内核的 MCU中 SPI所具有的大部分功能和SPI通信协议的特性,最终采用了通用强制性接口、通用非强制性接口和厂商库接口这三种类型的接口组合来共同完成SPI的接口定义,下面将分别对SPI的各个接口做介绍。

2.1 通用强制接口

通用强制接口是提取的一套 ARM Cortex M0/M3所有厂商系列 MCU都具有的功能接口。本篇将同样以新唐M051为例讲解CoX.SPI,其他系列大同小异。

SPI 的 APIs 分组完成处理配置和状态、处理数据和管理中断几大功能,CoX的宏定义的参数和APIs都是以' x '开头的, 函数和形式参数都是x开头,体现出CoX接口的特征。

配置对应的GPIO管脚线为SPI功能的函数:

        xSPinTypeSPI

SPI工作模式配置的函数:

        xSPIConfigSet
        xSPIEnable
        xSPIDisable
        xSPIDMAEnable
        xSPIDMADisable

SSn管教状态配置的函数:

        xSPISSSet

处理SPI中断的APIs

        xSPIIntCallbackInit
        xSPIIntEnable
        xSPIIntDisable
        xSPIStatusGet

获取SPI工作状态的APIs

        xSPIBitLengthGet
        xSPIIsBusy
        xSPIIsRxEmpty
        xSPIIsTxEmpty
        xSPIIsRxFull
        xSPIIsTxFull

处理SPI的数据读写的APIs

        xSPISingleDataReadWrite
        xSPIDataBufferRead
        xSPIDataBufferReadNonBlocking
        xSPIDataRead
        xSPIDataBufferWrite
        xSPIDataBufferWriteNonBlocking
        xSPIDataWrite

2.2 通用非强制接口

通用非强制接口是一类MCU通有的功能,而不是所有MCU都具有的功能接口:

        xSPIErrorGet
        xSPIErrorClear

2.3厂商库特色接口

特色接口是包括了通用性接口,和MCU特有功能的接口。它并不是通用强制型或者通用非强制型,而是MCU特有的功能,就是在厂商库特色接口这一组。相关的APIs接口如下:

        SPIAutoSSEnable
        SPIAutoSSDisable
        SPISSClear
        SPISSConfig
        SPILevelTriggerStatusGet
        SPIByteReorderSet
        SPIVariableClockSet
        SPIDivOneFunction
        SPI3WireFunction
        SPI3WireAbort
        SPI3WireStartIntEnable
        SPI3WireStartIntDisable
        SPI3WireStartIntFlagGet
        SPI3WireStartIntFlagClear

另外厂商库接口也实现了MCU其他所有的功能,比如:

SPIIntEnable(ulBase, ulIntFlags);

也实现了SPI中断使能/失能模式的配置,这个在CoX接口的xSPIIntEnable也是这个功能。其实这个时候xSPIIntEnable的实现方式如下:

#define xSPIIntEnable(ulBase,  ulIntFlags)                                     \
        SPIIntEnable(ulBase,  ulIntFlags)

进行了一次宏定义包装罢了,对应的参数也是进行的一次宏定义比如:

//
//! End of transfer
//
#define xSPI_INT_EOT            SPI_INT_EOT

3. 设计技巧简介

SPI模块的CoX接口在设计的过程中非常注重“上层应用简洁 下层驱动通用”的核心思想, CoX.SPI组件提供了一套寄存器组件, 对所需要写入寄存器的值进行了宏定义,这样使得用户在操作过程中不需深究寄存器的细节,使得配置更加直观,同时也方便用户调用寄存器接口定义自己的功能函数。下面以函数xSPIConfigSet(ulBase, ulBitRate, ulConfig)为例来说明:

比如将 MCU的SPI0模块配置成Master Mode , polarity 0 , phase 0 , 200KHz, 8Bits Data width,  SPI MSB First模式, 代码如下:

   xSPIConfigSet(xSPI0_BASE,  200000,  xSPI_MOTO_FORMAT_MODE_0  |
                                                               xSPI_MODE_MASTER  |
                                                               xSPI_MSB_FIRST |
                                                               xSPI_DATA_WIDTH8);

xSPIConfigSet()函数的定义是  xSPIConfigSet(ulBase, ulBitRate, ulConfig)  ,其中ulBase对应SPIn的基地址,可以取值xSPI0_BASE、xSPI1_BASE、……,ulBitRate是SPI传输和接收数据的速率,ulConfig是SPI的各种工作状态的组合。该函数的功能是非常丰富的,在执行完该函数后,SPI就完成了传输速率、数据格式、工作状态等配置。具体的实现过程如下:

#define xSPIConfigSet(ulBase, ulBitRate, ulConfig)                                                                 \
        do                                                                                                                                 \
        {                                                                                                                                   \
             SPIConfig(ulBase, ulBitRate, ulConfig);                                                                      \
             SPISSConfig(ulBase, SPI_SS_LEVEL_TRIGGER, SPI_SS_ACTIVE_LOW_FALLING);    \
        }                                                                                                                                   \
        while(0)

(1) 向SPI控制寄存器中分别写入数据格式、工作状态、数据宽度等配置信息

(2) 获取SPI的系统时钟,根据用户设定的时钟与获取的SPI系统时钟计算出分频系数和分频之后的值,然后将它们分别写入对应寄存器中。

(3) 配置SSn管脚的触发状态。

在这个功能函数中我们集成了两个函数,其中SPIConfig(ulBase, ulBitRate, ulConfig)为厂商库函数,这个接口刚好可以给CoX接口调用,做好相应的配置,不需要做特殊处理,这样可以最大程度的重用代码。同时为了方便用户,CoX.SPI会在初始化时选取一些默认状态配置,例如SPISSConfig(ulBase, SPI_SS_LEVEL_TRIGGER, SPI_SS_ACTIVE_LOW_FALLING);这里默认将SS脚配置成了低电平触发, 符合MCU大部分工作状态下SS管脚的配置,这样不仅可以减少用户的工作量,也使得代码更简洁,当然用户也可以使用CoX.SPI中的xSPISSSet()来自定义SS脚状态。

CoX.SPI另一个主要特色就是引入了阻塞式和非阻塞式的概念.我们在设计CoX.SPI接口的时候充分考虑实际编程的需求,相较于CoX1.0的数据处理,CoX2.0中特别增加了非阻塞式的数据发送和接收处理函数,可以满足不同情况下的SPI数据发送与接收。具体的函数名称如下:

非阻塞式发送:xSPIDataBufferWriteNonBlocking ()

非阻塞式接收:xSPIDataBufferReadNonBlocking()

阻塞式发送:xSPIDataBufferWrite()

阻塞式接收:xSPIDataBufferRead()

比如在一些采用循环查询SPI数据的系统中,MCU会不停的循环查询SPI的数据,这时如果我们采用一般的阻塞式的SPI数据接收,当SPI没有接收数据时,MCU会一直停在这等待,不断的查询SPI数据接收情况,而无法执行其他的任务,这显然不符合我们的要求,CoX1.0与厂商库都不能很好的解决这一问题,而在CoX2.0中,由于我们引入了非阻塞式数据接收与处理的概念,这个时候我们就可以采用非阻塞式的数据接收处理,让单片机在查询SPI数据为空时,可以继续执行下面的任务,等待下次来再来查询SPI数据状态,这样就可以很好的解决等待SPI接收数据与运行其它任务之间的矛盾问题。

4. SPI接口使用示例与移植

下面给出一个CoX.SPI的示例,都是使用的通用强制型的接口,因此下面的例子在所有Cortex M0/M3上都是平滑移植的, 下面展示一个程序。

//
// Users should to modify the following GPIO pins definition when transplanting the different
//MCUs based on Cortex-M3/M0.
//

#define SPICLK_PIN        PB7
#define SPIMOSI_PIN       PB5
#define SPIMISO_PIN       PB6
#define SPICS_PIN         PB4

void SpiReceiveTransfer (void)
{
    //
    // Enable Peripheral SPI0
    //

    xSysCtlPeripheralEnable2(xGPIOSPinToPort(SPICLK_PIN));
    xSysCtlPeripheralEnable2(xSPI0_BASE);

    //
    // Set SysClk 36MHz using Extern 12M oscillator
    //

    xSysCtlClockSet(12000000, xSYSCTL_OSC_MAIN | xSYSCTL_XTAL_12MHZ);
   
    //
    // Configure Some GPIO pins as SPI Mode
    //

    xSPinTypeSPI(SPI0CLK, SPICLK _PIN);
    xSPinTypeSPI(SPI0MOSI, SPIMOSI _PIN);
    xSPinTypeSPI(SPI0MISO, SPIMISO _PIN);
    xSPinTypeSPI(SPI0CS, SPICS _PIN);

xSPIConfigSet(xSPI0_BASE, 200000,  xSPI_FORMAT_MODE_0  | xSPI_DATA_WIDTH32    |
                                                           xSPI_MSB_FIRST  |  xSPI_MODE_SLAVE);      
 
    while(1)
    {
        for(i = 0; i < 16; i++)
        {
            ulDestData[i] = xSPISingleDataReadWrite(xSPI0_BASE, ulSourceData[i]);
        }  
    }
}

.END

No comments:

Post a Comment