1. 贡献者列表

DJYOS开发团队。

 

2. 概述

DJYOSDjyBus总线模型为IICSPI之类的器件提供统一的访问接口,SPIBUS模块是DjyBus模块的一个子模块,为SPI器件提供统一的编程接口,实现通信协议层与器件层的分离。也标准化了SPI总线和 Device驱动接口,本手册指导驱动工程师编写SPI的接口程序。

SPI总线使用手册,请参见《都江堰操作系统用户手册》。

局限性:DJYOSV1.1.1版本的SPI驱动只提供主设备功能。

3. 总线资源结构

SPI通信协议是一种总线通信方式,这意味着一条总线上可以挂多个符合总线通信协议的设备,DjyBus资源组织结构就是符合这样一种物理的连接方式。如 21所示资源组织结构图,总线类型 SPI”、第n条总线“SPIn”、第n条总线上面的设备“Devn”,它们都是DjyBus资源树上面的资源节点,每次向总线“SPIn”上面增加一个设备,便向资源树上面增加了一个资源节点,它是“SPIn”的子节点。

21 总线资源结构

4. 准备工作

在编写SPI 器件驱动前,建议完成必要的准备工作,如:

1、  认真阅读器件手册,了解通信协议、参数、操作流程等内容;

2、  熟悉spibus.h头文件中提供的API,懂得参数的使用方法;

3、  阅读SPI总线协议文档,熟练掌握SPI总线。

5.SPI 总线驱动接口

5.1. 驱动架构

SPIBusDjyBus模块的一个子模块,其结构如 41所示,它为SPI器件提供标准的、一致的应用程序编程接口,并且规范了硬件驱动接口。驱动接口分为总线控制器接口和SPI器件接口两部分,驱动的重点是总线控制器,而器件接口实际上就是配置一下该器件的物理参数。

建议文件路径:eclipse工程中的链接目录如下,如果是导入官方提供的example工程,那么该目录已经建立,在硬盘中添加文件后,只需要刷新工程即会自动添加进工程中。

src->OS_code->bsp->cpudrv->src->cpu_peri_spi.c

相应的头文件目录为:

src->OS_code->bsp->cpudrv-> src->cpu_peri_spi.h

在文件系统(硬盘)中的目录结构是:

djysrc\bsp\cpudrv\cpu_name\src\cpu_peri_spi.c

djysrc\bsp\cpudrv\cpu_name\include\cpu_peri_spi.h

根据以上命名,可以在DJYOS官方提供的代码中,找到大量范例。

以上文件命名并非绝对,例如LPC17xxSPI模块,硬件被官方命名为SSP模块,DJYOS提供的源码中,其文件名就命名为cpu_peri_ssp.c

SPI驱动程序编写重点有:

1、  初始化SPI控制器,并且把SPI总线添加到DjyBus上。

2、  实现 41中的5个回调函数(哪些需要实现,参考后续章节)。

3、  如果采用中断方式,须编写中断服务函数(实际上也是为4个回调函数服务)

41 SPIBus总线驱动架构

5.2. 初始化函数

5.2.1. Step1:初始化硬件

1、  SPI控制器硬件的初始化,包括传输速度、IO配置、时钟等;

2、  挂载SPI中断到中断系统,并配置中断类型,如配置为异步信号(若只采用轮询方式,则此功能可省略);

5.2.2. Step2:初始化参数结构体

添加SPI总线的参数类型为struct tagSPI_Param,由函数SPI_BusAddSPI_BusAdd_s完成对SPI总线控制块的初始化和添加SPIn总线节点到DjyBus资源树。

代码 41 SPI参数结构体定义

struct tagSPI_Param

{

    char            *BusName;               //总线名称,如SPI1

    u8              *SPIBuf;                //总线缓冲区指针

    u32              SPIBufLen;             //总线缓冲区大小,字节

    ptu32_t         SpecificFlag;         //SPI私有标签,如控制寄存器基址

    bool_t           MultiCSRegFlag;      //SPI控制寄存器是否有多套CS配置寄存器

    TransferFunc    pTransferTxRx;       //发送接收回调函数,中断方式

    TransferPoll    pTransferPoll;       //发送接收回调函数,轮询方式

    CsActiveFunc    pCsActive;           //片选使能

    CsInActiveFunc  pCsInActive;         //片选失能

    SPIBusCtrlFunc  pBusCtrl;             //控制函数

};

SPI主设备同一时刻只能与一个从设备通信,收发同时进行,因此同一个SPI控制器中,多个片选可以共用缓冲区。

很多的SPI控制器对每个片选都提供一套配置通信参数寄存器,例如,CS0与从设备通信采用速度5Mbit/s,字符宽度为8比特,MSB,对应的配置片选CS0对应的寄存器,而CS1的从设备采用速度10Mbit/s,字符宽度为16比特,LSB,对应的配置片选CS1对应的寄存器。这种增强型的控制器对于一主多从,参数不一的通信能大大提高通信效率,简化参数配置。

SPI参数结构体的回调函数参数的原型如代码 42所示,其中PrivateTag就是结构体中SPI的私有标签,即SPIn寄存器基址。

代码 42 SPI回调函数类型申明

typedef ptu32_t (*TransferFunc)(ptu32_t SpecificFlag,u32 sendlen,u32 recvlen,u32 recvoff);

typedef bool_t (*TransferPoll)(ptu32_t SpecificFlag,u8* srcaddr,u32 sendlen,u8* destaddr,u32 recvlen,u32 recvoff);

typedef bool_t (*CsActiveFunc)(ptu32_t SpecificFlag, u8 cs);

typedef bool_t (*CsInActiveFunc)(ptu32_t SpecificFlag, u8 cs);

typedef ptu32_t (*SPIBusCtrlFunc)(ptu32_t SpecificFlag,u32 cmd,ptu32_t data1,ptu32_t data2);

 

5.2.3. Step3:挂载总线

有多少SPI总线是由具体的平台决定,因此,增加SPI总线到DjyBus上是由总线驱动程序员完成,成功添加的“SPIn”节点会成为“SPI”节点的子节点。

增加SPI总线的API函数可以调用SPI_BusAdd函数或SPI_BusAdd_s函数,两者的区别在于,SPI_BusAdd只需调用者提供已初始化好的参数结构体struct tagSPI_Param,而后者更需要提供struct tagSPI_CB结构体控制块(建议定义为静态变量)。

5.3. 回调函数

5.3.1. 轮询函数

如果采用轮询方式收发,5个回调函数中只需要实现这一个,其他指针置为NULL即可。

轮询函数使用场合:

1、    收发方式被设为轮询方式,则总是用轮询函数收发数据。默认值为中断方式,可调用SPI_BusCtrl函数设为轮询方式。

2、    在禁止调度(即禁止异步信号中断)期间,强制使用轮询方式。

3、    pTransferTxRx ==NULL,则使用轮询方式收发。

4、    系统初始化未完成,多事件调度尚未启动期间。

如果使用中断方式收发,且不考虑在2~4三种情况下收发数据,则无须实现本函数,pTransferPoll指针设为NULL即可。

回调函数说明如下:

typedef bool_t (*TransferPoll)(ptu32_t SpecificFlag,u8* srcaddr,u32 sendlen,

                                    u8* destaddr,u32 recvlen,u32 recvoff);

参数:

       SpecificFlag寄存器基址

       srcaddr:发送数据存储地址。

       sendlen:发送数据字节数。

       destaddr:接收数据存储地址。

       recvlen:接收数据字节数。

       recvoff:接收偏移字节数,即认为接收到recvoff字节后的数据为有效,才存储。

返回:true,执行成功;false,执行失败。

说明:轮询方式主要应用于系统调度未启动或对实时性要求不高的场合,这种方法能够简化编程处理,快速实现通信功能。

5.3.2. 启动收发

启动收发是在使用中断方式收发数据必须实现的回调函数,若使用轮询方式,无须实现本函数,本函数指针设置为NULL即可。

回调函数说明如下:

typedef ptu32_t (*TransferFunc)(ptu32_t SpecificFlag,u32 sendlen,

u32 recvlen,u32 recvoff);

参数:

SpecificFlag寄存器基址。

       sendlen,发送数据长度,字节单位。

       recvlen,接收数据长度,字节单位。

       recvoff,接收偏移,接收到多少个字节后开始保护数据,即有用数据。

返回:true,中断方式启动通信成功,false,失败。

说明:该函数功能是配置SPI寄存器,并保存有关参数,使能中断,SPI总线采用的是四线的收发方式,收、发、时钟和片选。TransferFunc实质上是实现了相关的寄存器的配置,并中断使能。当然,SPI总线协议规定,操作设备前必须把对应从设备的CS线拉低。

对于TransferFunc需要完成的功能作如下说明:

1、  保存静态变量,如发送接收数据长度,接收偏移(从接收到的第几个数据开始保存数据);

2、  配置SPI寄存器,使其处于发送和接收的状态;

3、  配置中断使能,并触发中断,在中断中将数据发送接收完成。

5.3.3. 片选使能

若使用轮询方式,本函数指针设置为NULL即可,但使能片选的功能须实现。

typedef bool_t (*CsActiveFunc)(ptu32_t SpecificFlag, u8 cs);

功能:片选拉低

参数:

SpecificFlag寄存器基址

    cs,片选号

返回:是否成功

虽然对于具体的芯片,该函数的实现过程不相同,但是功能是相同的,即拉低片选,选择CS对应的SPI从器件通信。若控制器具有硬件自动片选,硬件驱动可加以利用,提高效率。

5.3.4. 片选失能

若使用轮询方式,本函数指针设置为NULL即可,但失能片选的功能须实现。

typedef bool_t (*CsInActiveFunc)(ptu32_t SpecificFlag, u8 cs);

功能:片选拉高

参数:

SpecificFlag寄存器基址

    cs,片选号

返回:是否成功

5.3.5. 控制函数

目前,控制函数主要实现对总线的配置,如自动片选、传输速度、SPI时序配置等。应用层将通过调用SPI_Ctrl接口函数,传递不同的命令和参数,实现对总线的控制。

如果SPI控制器针对每个片选信号都有独立的配置寄存器,则在添加设备时(SPI_DevAddr),配置好每个片选寄存器;若多个片选共用一套配置寄存器,则每次传输都必须重新配置。在结构体SPI_CB中,成员multi_cs_reg是用来标记SPI控制器是否具有多套片选寄存器,在调用ModuleInstall_SPI时,硬件驱动会作相应的标记。

SPI协议的时序来讲配置参数会更加的清晰。如 42 43所示,SPI通信首先需产生CS片选有效,即拉低对应的CS片选。时钟信号SPI_CLK在未通信状态时的电平状态由CHOL决定,为高或者为低。而CPHA决定时序的相位,当CPHA0时,在SPI_CLK的第一个边沿采样,第二个边沿输出数据;当CPHA1时,在SPI_CLK的第一个边沿输出数据,第二个边沿采样。

42 SPI时序CPHA=0

43 SPI时序CPHA=1

CHOLCPHA两种配置组成了四种模式,分别为模式0123,如 41所示,部分SPI从器件只支持部分模式,需根据具体器件配置成要求的模式。

41 SPI模式

CHOL

CPHA

MODE

0

0

0

0

1

1

1

0

2

1

1

3

       控制命令用于应用层调用spibus.hAPI的控制函数SPI_Ctrl实现参数配置或通信设置,与硬件相关的命令一览表如 42所示。

42 SPI命令表

命令

参数1

参数2

说明

CN_SPI_SET_CLK

速率,bit/s

无意义

设置传输速度

CN_SPI_CS_CONFIG

tagSpiConfig

无意义

配置相关参数

CN_SPI_SET_AUTO_CS_EN

无意义

无意义

自动片选使能

CN_SPI_SET_AUTO_CS_DIS

无意义

无意义

自动片选失能

5.4. 中断服务函数

5.4.1. 功能描述

如果使用轮询方式实现驱动,则无须编写中断服务函数。

SPI接收和发送使用中断方式的好处在于,将发送任务由SPI控制器完成,节省CPU的处理负荷,因此提高了程序的运行效率,缺点在于编程相对复杂。现在绝大多数的主流CPU的中断系统都支持SPI中断,包括发送接收中断等。

SPI模块要求在中断服务函数内部完成的功能有如下:

1、  清中断标志,处理好接收与发送数据同时进行的硬件机制;

2、  接收数据从接收到recvoff字符数据后开始存储,即调用SPI_PortWrite

3、  发送数据从SPI_PortRead读取,若没有读到数据,则代表数据已经发送完成;若此时接收的数据还未完成,应该继续往寄存器中写数据,直到接收完成;

4、  数据传输完成时,配置相应的寄存器,使其处于初始状态(视控制器而定);

5.4.2. 中断实现过程

下面以Atmel芯片为例,通过流程图的方式简要说明SPI中断服务函数的数据处理流程图。值得注意的是,中断服务函数中有些变量是通过__SPI_TransferTxRx传递参数到底层硬件驱动,底层驱动通过静态变量存储,并在中断服务函数中使用。如发送接收数据大小,信号量等。

44 中断服务函数流程图

5.5. 移植建议

为了简化编程,提高工作效率, BSP程序人员可采取下面的步骤快速的完成DJYOS驱动架构下SPI底层驱动的开发。

1、  拷贝其他工程已测试通过的SPI驱动文件cpu_peri_spi.c/cpu_peri_spi.h

2、  添加SPI的中断号到critical.c文件下面tg_IntUsed组;

3、  修改cpu_peri_spi.c/cpu_peri_spi.h中与具体SPI寄存器相关的部分;

4、  回调函数的具体实现和中断收发数据。

调用器件驱动程序前,确保已经调用ModuleInstall_DjyBusModuleInstall_SPIBus安装DjyBusSPIBus模块。

 

6. SPI器件驱动接口

建议将器件驱动的存放目录为djysrc\bsp\chip\xxx,其中,xxx是具体芯片的文件夹名称。

SPI总线初始化完成后,添加一个器件到总线上的过程,非常简单,就是初始化一下该器件的寻址特性参数,然后调用SPI_DevAdd_sSPI_DevAdd函数把器件添加到总线上即可。需配置的参数,都在spibus.h文件中定义的struct tagSPI_Device中描述。struct tagSPI_Device结构定义如下:

代码 51 SPI器件结构体

//SPI总线器件结构体

struct tagSPI_Device

{

    struct tagRscNode DevNode;

    u8 Cs;                                   //片选信号

    bool_t AutoCs;                          //自动片选

    u8 CharLen;                             //数据长度

    u8 Mode;                                //模式选择

    u8 ShiftDir;                            //MSB or LSB

    u32 Freq;                               //速度,Hz

};

 

6.1. 初始化过程

添加器件到总线的过程就是将器件节点挂到相应的“SPIn”总线节点的过程,同时,配置好相应的总线通信参数。现对添加SPI器件要点作如下说明:

1、  若使用SPI_DevAdd_s挂载器件,定义static struct tagSPI_Device类型的静态变量;

2、  若使用SPI_DevAdd_s挂载器件,初始化数据struct tagSPI_Param的各成员;

3、  调用SPI_DevAdd_sSPI_DevAdd添加设备到总线节点。

4、  调用SPI_BusCtrl设置总线参数

SPI_DevAdd_sSPI_DevAdd都可以把器件添加到总线上,但两者是有区别的:

1、    使用SPI_DevAdd_s的话,你需要自己准备struct tagSPI_Device结构,并且自行初始化,特别是,当操作系统的spibus模块被修改导致该结构的定义发生变化时,器件驱动程序也需要修改。

2、    使用SPI_DevAdd_s的好处是,该结构无须动态分配,符合像OSEK之类的严谨规范。

3、    使用SPI_DevAdd的好处是,驱动程序非常简单。

 

下面用ATMEL公司的AT45EEPROM芯片为例说明添加设备过程。如代码 5‑2所示,将AT45芯片添加到总线“SPI”,并命名为“SPI_Dev_AT45”。

代码 52 添加SPI设备实例

bool_t AT45_HardInit(void)

{

    bool_t result = false;

    if(s_AT45_InitFlag == true)

        return true;

    static struct tagSPI_Device s_AT45_Dev;

 

    s_AT45_Dev.AutoCs     = false;

    s_AT45_Dev.CharLen   = 8;

    s_AT45_Dev.Cs         = CN_AT45_SPI_CS;

    s_AT45_Dev.Freq       = CN_AT45_SPI_FRE;

    s_AT45_Dev.Mode       = SPI_MODE_1;

    s_AT45_Dev.ShiftDir = SPI_SHIFT_MSB;

 

    if(NULL != SPI_DevAdd_s("SPI","SPI_Dev_AT45",&s_AT45_Dev))

    {

        ps_AT45_Dev = &s_AT45_Dev;

 

        if(true == _at45db321_Check_ID())  //校验芯片ID

        {

            _at45db321_Binary_Page_Size_512();

            s_AT45_InitFlag = true;

            result = true;

        }

    }

    return result;

}

 

7. 访问器件

器件装载到装载总线之后,可以通过访问SPI总线实现访问器件,具体就是调用spibus.h提供的API函数SPI_Transfer()。

powered by GitbookFile Modify: 2023-12-25 10:02:13

results matching ""

    No results matching ""